Created attachment 1946731[details]
reproducer_not_working.c
Description of problem:
Hi,
recent raise of _FORTIFY_SOURCE flag to level 3 caused Autogens test suite
to fail and while debugging this issue i found very interesting behavior
of fread function if used with level 3 of fortification. As i understand
it, fortification level 3 of fread function causes that the function
checks how big is the buffer where it is supposed to write the read bytes
and if it is not big enough then abort programs execution.
You can see that in this code snippet:
################################# libio/bits/stdio.h
__fortify_function __wur size_t
fread_unlocked (void *__restrict __ptr, size_t __size, size_t __n,
FILE *__restrict __stream)
{
size_t sz = __glibc_objsize0 (__ptr);
if (__glibc_safe_or_unknown_len (__n, __size, sz))
{
# ifdef __USE_EXTERN_INLINES
if (__builtin_constant_p (__size)
&& __builtin_constant_p (__n)
&& (__size | __n) < (((size_t) 1) << (8 * sizeof (size_t) / 2))
&& __size * __n <= 8)
{
size_t __cnt = __size * __n;
char *__cptr = (char *) __ptr;
if (__cnt == 0)
return 0;
for (; __cnt > 0; --__cnt)
{
int __c = getc_unlocked (__stream);
if (__c == EOF)
break;
*__cptr++ = __c;
}
return (__cptr - (char *) __ptr) / __size;
}
# endif
return __fread_unlocked_alias (__ptr, __size, __n, __stream);
}
if (__glibc_unsafe_len (__n, __size, sz))
return __fread_unlocked_chk_warn (__ptr, sz, __size, __n, __stream);
return __fread_unlocked_chk (__ptr, sz, __size, __n, __stream);
}
#################################
Now pay attention to particular line where sz is set to object size.
size_t sz = __glibc_objsize0 (__ptr);
I think that GCC is able to confuse this function and make it return
incorrect sizes and that causes the following __fread_unlocked_chk
call think that there is buffer overflow. When specific conditions
are achieved. You could say that this is most likely caused by glibc
and not by gcc and the __glibc_objsize0 is not working correctly but
i think that this is either gcc problem or combination of specific
conditions in both the __glibc_objsize0 function and gccs way of
compilation of the problematic code. This is because i've been
able to trigger this issue by seemingly not related code.
This code in working version of the reproducer works:
################################# reproducer_working.c
do {
scaning_context->buf_size *= 2;
scaning_context->data_offset += scaning_context->byte_read;
scaning_context = realloc(scaning_context, scaning_context->buf_size + 1 + sizeof(*scaning_context));
fprintf(stderr, "Realloced\n");
if (scaning_context == NULL) {
printf("Error while reallocating\n");
return 1;
}
reading_pointer = ((char *)(scaning_context + 1)) + scaning_context->data_offset;
scaning_context->data = (char *) (scaning_context+1);
} while (scaning_context->byte_read = fread((void *)reading_pointer, 1, scaning_context->buf_size - scaning_context->data_offset, stdin));
#################################
__glibc_objsize0 correctly determines remaining size in the buffer and code works as expected.
On the other hand this code does not work:
################################# reproducer_not_working.c
context_t *p;
do {
scaning_context->buf_size *= 2;
scaning_context->data_offset += scaning_context->byte_read;
p = realloc(scaning_context, scaning_context->buf_size + 1 + sizeof(*scaning_context));
fprintf(stderr, "Realloced\n");
if (p == NULL) {
printf("Error while reallocating\n");
return 1;
}
if (p != scaning_context) {
fprintf(stderr, "Context has been moved!\n");
scaning_context = p;
}
reading_pointer = ((char *)(scaning_context + 1)) + scaning_context->data_offset;
scaning_context->data = (char *) (scaning_context+1);
} while (scaning_context->byte_read = fread((void *)reading_pointer, 1, scaning_context->buf_size - scaning_context->data_offset, stdin));
#################################
I checked and buffer overflow condition is triggered even when scaning_context
has not been moved and the scaning_context memory area has just been extended.
It seems that the additional pointer "p" in combination with casting of
reading_pointer to "(void *)" manages to break the __glibc_objsize0
functions ability to find out correct size of remaining area.
If i remove the cast to "(void *)" then everything works as expected.
In the same time, when address sanitizer or valgrind is used then
no buffer overflow is found and this works again as expected.
Version-Release number of selected component (if applicable):
gcc-13.0.1-0.4.fc39
glibc-2.37-1.fc38
kernel-6.2.0-0.rc8.20230217gitec35307e18ba.60.fc39
How reproducible:
Always.
Steps to Reproduce:
1. gcc -O2 -g -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 ./reproducer_not_working.c -o ./reproducer_not_working
2. ./reproducer_not_working < ./stress.samp
Actual results:
Realloced
*** buffer overflow detected ***: terminated
Aborted (core dumped)
Expected results:
testing file is printed out and no buffer overflow is detected.
Additional info:
The second reproducer reproducer_working.c works as expected.
I added it to make the problem more visible.
Do not hesitate to ask further questions.
Thanks for any help.
It has not. It did not occur to me to look for an upstream issue
when i realized that this is most likely a GCC problem and i wanted
to point this out in case that it could cause more issues.
Will apply autogen patch. Thanks for your help, feel free to close this.
Created attachment 1946731 [details] reproducer_not_working.c Description of problem: Hi, recent raise of _FORTIFY_SOURCE flag to level 3 caused Autogens test suite to fail and while debugging this issue i found very interesting behavior of fread function if used with level 3 of fortification. As i understand it, fortification level 3 of fread function causes that the function checks how big is the buffer where it is supposed to write the read bytes and if it is not big enough then abort programs execution. You can see that in this code snippet: ################################# libio/bits/stdio.h __fortify_function __wur size_t fread_unlocked (void *__restrict __ptr, size_t __size, size_t __n, FILE *__restrict __stream) { size_t sz = __glibc_objsize0 (__ptr); if (__glibc_safe_or_unknown_len (__n, __size, sz)) { # ifdef __USE_EXTERN_INLINES if (__builtin_constant_p (__size) && __builtin_constant_p (__n) && (__size | __n) < (((size_t) 1) << (8 * sizeof (size_t) / 2)) && __size * __n <= 8) { size_t __cnt = __size * __n; char *__cptr = (char *) __ptr; if (__cnt == 0) return 0; for (; __cnt > 0; --__cnt) { int __c = getc_unlocked (__stream); if (__c == EOF) break; *__cptr++ = __c; } return (__cptr - (char *) __ptr) / __size; } # endif return __fread_unlocked_alias (__ptr, __size, __n, __stream); } if (__glibc_unsafe_len (__n, __size, sz)) return __fread_unlocked_chk_warn (__ptr, sz, __size, __n, __stream); return __fread_unlocked_chk (__ptr, sz, __size, __n, __stream); } ################################# Now pay attention to particular line where sz is set to object size. size_t sz = __glibc_objsize0 (__ptr); I think that GCC is able to confuse this function and make it return incorrect sizes and that causes the following __fread_unlocked_chk call think that there is buffer overflow. When specific conditions are achieved. You could say that this is most likely caused by glibc and not by gcc and the __glibc_objsize0 is not working correctly but i think that this is either gcc problem or combination of specific conditions in both the __glibc_objsize0 function and gccs way of compilation of the problematic code. This is because i've been able to trigger this issue by seemingly not related code. This code in working version of the reproducer works: ################################# reproducer_working.c do { scaning_context->buf_size *= 2; scaning_context->data_offset += scaning_context->byte_read; scaning_context = realloc(scaning_context, scaning_context->buf_size + 1 + sizeof(*scaning_context)); fprintf(stderr, "Realloced\n"); if (scaning_context == NULL) { printf("Error while reallocating\n"); return 1; } reading_pointer = ((char *)(scaning_context + 1)) + scaning_context->data_offset; scaning_context->data = (char *) (scaning_context+1); } while (scaning_context->byte_read = fread((void *)reading_pointer, 1, scaning_context->buf_size - scaning_context->data_offset, stdin)); ################################# __glibc_objsize0 correctly determines remaining size in the buffer and code works as expected. On the other hand this code does not work: ################################# reproducer_not_working.c context_t *p; do { scaning_context->buf_size *= 2; scaning_context->data_offset += scaning_context->byte_read; p = realloc(scaning_context, scaning_context->buf_size + 1 + sizeof(*scaning_context)); fprintf(stderr, "Realloced\n"); if (p == NULL) { printf("Error while reallocating\n"); return 1; } if (p != scaning_context) { fprintf(stderr, "Context has been moved!\n"); scaning_context = p; } reading_pointer = ((char *)(scaning_context + 1)) + scaning_context->data_offset; scaning_context->data = (char *) (scaning_context+1); } while (scaning_context->byte_read = fread((void *)reading_pointer, 1, scaning_context->buf_size - scaning_context->data_offset, stdin)); ################################# I checked and buffer overflow condition is triggered even when scaning_context has not been moved and the scaning_context memory area has just been extended. It seems that the additional pointer "p" in combination with casting of reading_pointer to "(void *)" manages to break the __glibc_objsize0 functions ability to find out correct size of remaining area. If i remove the cast to "(void *)" then everything works as expected. In the same time, when address sanitizer or valgrind is used then no buffer overflow is found and this works again as expected. Version-Release number of selected component (if applicable): gcc-13.0.1-0.4.fc39 glibc-2.37-1.fc38 kernel-6.2.0-0.rc8.20230217gitec35307e18ba.60.fc39 How reproducible: Always. Steps to Reproduce: 1. gcc -O2 -g -Wp,-U_FORTIFY_SOURCE,-D_FORTIFY_SOURCE=3 ./reproducer_not_working.c -o ./reproducer_not_working 2. ./reproducer_not_working < ./stress.samp Actual results: Realloced *** buffer overflow detected ***: terminated Aborted (core dumped) Expected results: testing file is printed out and no buffer overflow is detected. Additional info: The second reproducer reproducer_working.c works as expected. I added it to make the problem more visible. Do not hesitate to ask further questions. Thanks for any help.