https://gcc.gnu.org/bugzilla/show_bug.cgi?id=94849
            Bug ID: 94849
           Summary: Improper parameter validation in libsanitizer for
                    fopen64
           Product: gcc
           Version: 9.3.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: sanitizer
          Assignee: unassigned at gcc dot gnu.org
          Reporter: rachel at rachelmant dot com
                CC: dodji at gcc dot gnu.org, dvyukov at gcc dot gnu.org,
                    jakub at gcc dot gnu.org, kcc at gcc dot gnu.org, marxin at 
gcc dot gnu.org
  Target Milestone: ---

Created attachment 48408
  --> https://gcc.gnu.org/bugzilla/attachment.cgi?id=48408&action=edit
A simple fix for the bug

fopen() (actually fopen64 because of macro remapping) as implemented by Glibc
and other C run-times allows the first "path" or "filename" parameter to be
nullptr and causes the function to simply do nothing and return nullptr itself
when this happens.

Because of this, I have a test case in a project that is specifically verifying
this bad, but allowed, use of fopen64() as a way to guarantee it returns
nullptr for some further tests and for code coverage reasons.

The simplest form of failing program is:

```
#include <cstdio>

int main()
{
    return !fopen(nullptr, "w") ? 0 : 1;
}
```

which when compiled with `g++ -D_FILE_OFFSET_BITS=64 -o test test.cxx` runs and
returns 0, but when compiled with `g++ -D_FILE_OFFSET_BITS=64 -o test
-fsanitize=address test.cxx` or the thread sanitizer, instead crashes:

```
$ ./test; echo $?
AddressSanitizer:DEADLYSIGNAL
=================================================================
==2187382==ERROR: AddressSanitizer: SEGV on unknown address 0x000000000000 (pc
0x7f8867d791e5 bp 0x7ffcc8f95db0 sp 0x7ffcc8f95528 T0)
==2187382==The signal is caused by a READ memory access.
==2187382==Hint: address points to the zero page.
    #0 0x7f8867d791e4 in __strlen_avx2 (/usr/lib/libc.so.6+0x1611e4)
    #1 0x7f886817cc35 in __interceptor_fopen64
/build/gcc/src/gcc/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc:5757
    #2 0x56367982918d in main (/tmp/test+0x118d)
    #3 0x7f8867c3f022 in __libc_start_main (/usr/lib/libc.so.6+0x27022)
    #4 0x5636798290ad in _start (/tmp/test+0x10ad)

AddressSanitizer can not provide additional info.
SUMMARY: AddressSanitizer: SEGV (/usr/lib/libc.so.6+0x1611e4) in __strlen_avx2
==2187382==ABORTING
1

```

This is on GCC 9.3.0, but I have reproduced this on everything from GCC 5
through to 9. Please note: This is not a problem when the non-64-bit fopen() is
used. This is specifically a problem with fopen64().

Unfortunately, libsanitizer does not properly validate the path parameter and
ends up calling strlen() on a nullptr - which is UB that on x86 crashes with
SEGV as seen above.

After triggering the bug and doing some research, it appears that since the
introduction of libsanitizer, this has been a thing:
https://github.com/gcc-mirror/gcc/blob/master/libsanitizer/sanitizer_common/sanitizer_common_interceptors.inc#L5882

Attached is a patch that would fix this bugged behaviour.

Reply via email to