Re: [PATCH] libsanitizer: Fix 'unknown-crash' reported for partial buffer overflows
Note: This patch is currently in discussion on llvm-project's side and may have minor tweaks. Once that's done, the patch will be redone by applying upstream changes. Wern On 13/6/25 12:40 pm, Wern Lim wrote: Given a partially misaligned memory read for a large number of bytes (e.g., we allocate data at addr [0, 16) but read addr [2, 18)), the address sanitizer (asan) would flag the error as an 'unknown-crash' instead of a 'stack-buffer-overflow' when compiled with gcc. This is due to a flawed heuristic in asan_errors.cpp, which won't always locate the appropriate shadow byte that would indicate a buffer overflow for any 8 byte or 16+ byte reads. This asan bug impacts gcc, but not clang, as they seem to report errors differently - gcc-compiled binaries report the starting address of the read attempt to asan, while clang-compiled binaries highlight the first byte access that overflows the buffer. This appears to have always been the case at least as far back as gcc-7. This patch resolves this issue via a linear scan of applicable shadow bytes (instead of the original heuristic, which, at best, only increments the shadow byte address by 1 for these scenarios). It also adds appropriate tests to cover for these cases. --- .../c-c++-common/asan/stack-overflow-1.c | 2 ++ .../stack-overflow-multi-byte-partial-1.c | 27 +++ .../stack-overflow-multi-byte-partial-2.c | 27 +++ .../stack-overflow-multi-byte-partial-3.c | 27 +++ .../stack-overflow-multi-byte-partial-4.c | 27 +++ .../stack-overflow-multi-byte-partial-5.c | 27 +++ .../stack-overflow-multi-byte-partial-6.c | 27 +++ .../stack-overflow-multi-byte-partial-7.c | 27 +++ .../stack-overflow-multi-byte-partial-8.c | 27 +++ libsanitizer/asan/asan_errors.cpp | 7 +++-- 10 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-2.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-3.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-4.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-5.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-6.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-7.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-8.c diff --git a/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c b/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c index 7eab79c6514..c76294b6765 100644 --- a/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c +++ b/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c @@ -17,7 +17,9 @@ int main() { return res; } +/* { dg-output "ERROR: AddressSanitizer:? stack-buffer-overflow on address\[^\n\r]*(\n|\r\n|\r)" } */ /* { dg-output "READ of size 1 at 0x\[0-9a-f\]+ thread T0\[^\n\r]*(\n|\r\n|\r)" } */ /* { dg-output "#0 0x\[0-9a-f\]+ +(in _*main (\[^\n\r]*stack-overflow-1.c:16|\[^\n\r]*:0|\[^\n\r]*\\+0x\[0-9a-z\]*)|\[(\]).*(\n|\r\n|\r)" } */ /* { dg-output "\[^\n\r]*Address 0x\[0-9a-f\]+ is located in stack of thread T0.*(\n|\r\n|\r)" */ +/* { dg-output ".*Memory access at offset \[0-9\]+ overflows this variable.*(\n|\r\n|\r)" } */ /* { dg-output "\[^\n\r]*in main.*stack-overflow-1.c.*(\n|\r\n|\r)" */ diff --git a/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c b/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c new file mode 100644 index 000..8a4fef2be32 --- /dev/null +++ b/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c @@ -0,0 +1,27 @@ +/* { dg-do run } */ +/* { dg-shouldfail "asan" } */ + +#define STACK_ALLOC_SIZE 8 +#define READ_SIZE 8 +#define OFFSET 1 + +struct X { + char bytes[READ_SIZE]; +}; + +__attribute__((noinline)) struct X out_of_bounds() { + volatile char bytes[STACK_ALLOC_SIZE]; + struct X* x_ptr = (struct X*)(bytes + OFFSET); + return *x_ptr; +} + +int main() { + struct X x = out_of_bounds(); + return x.bytes[0]; +} + +/* { dg-output "ERROR: AddressSanitizer:? stack-buffer-overflow on address\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "READ of size 8 at 0x\[0-9a-f\]+ thread T0\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*Address 0x\[0-9a-f\]+ is located in stack of thread T0.*(\n|\r\n|\r)" */ +/* { dg-output ".*Memory access at offset \[0-9\]+ partially overflows this variable.*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*in out_of_bounds.*stack-
[PATCH] libsanitizer: Fix 'unknown-crash' reported for partial buffer overflows
Given a partially misaligned memory read for a large number of bytes (e.g., we allocate data at addr [0, 16) but read addr [2, 18)), the address sanitizer (asan) would flag the error as an 'unknown-crash' instead of a 'stack-buffer-overflow' when compiled with gcc. This is due to a flawed heuristic in asan_errors.cpp, which won't always locate the appropriate shadow byte that would indicate a buffer overflow for any 8 byte or 16+ byte reads. This asan bug impacts gcc, but not clang, as they seem to report errors differently - gcc-compiled binaries report the starting address of the read attempt to asan, while clang-compiled binaries highlight the first byte access that overflows the buffer. This appears to have always been the case at least as far back as gcc-7. This patch resolves this issue via a linear scan of applicable shadow bytes (instead of the original heuristic, which, at best, only increments the shadow byte address by 1 for these scenarios). It also adds appropriate tests to cover for these cases. --- .../c-c++-common/asan/stack-overflow-1.c | 2 ++ .../stack-overflow-multi-byte-partial-1.c | 27 +++ .../stack-overflow-multi-byte-partial-2.c | 27 +++ .../stack-overflow-multi-byte-partial-3.c | 27 +++ .../stack-overflow-multi-byte-partial-4.c | 27 +++ .../stack-overflow-multi-byte-partial-5.c | 27 +++ .../stack-overflow-multi-byte-partial-6.c | 27 +++ .../stack-overflow-multi-byte-partial-7.c | 27 +++ .../stack-overflow-multi-byte-partial-8.c | 27 +++ libsanitizer/asan/asan_errors.cpp | 7 +++-- 10 files changed, 223 insertions(+), 2 deletions(-) create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-2.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-3.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-4.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-5.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-6.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-7.c create mode 100644 gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-8.c diff --git a/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c b/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c index 7eab79c6514..c76294b6765 100644 --- a/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c +++ b/gcc/testsuite/c-c++-common/asan/stack-overflow-1.c @@ -17,7 +17,9 @@ int main() { return res; } +/* { dg-output "ERROR: AddressSanitizer:? stack-buffer-overflow on address\[^\n\r]*(\n|\r\n|\r)" } */ /* { dg-output "READ of size 1 at 0x\[0-9a-f\]+ thread T0\[^\n\r]*(\n|\r\n|\r)" } */ /* { dg-output "#0 0x\[0-9a-f\]+ +(in _*main (\[^\n\r]*stack-overflow-1.c:16|\[^\n\r]*:0|\[^\n\r]*\\+0x\[0-9a-z\]*)|\[(\]).*(\n|\r\n|\r)" } */ /* { dg-output "\[^\n\r]*Address 0x\[0-9a-f\]+ is located in stack of thread T0.*(\n|\r\n|\r)" */ +/* { dg-output ".*Memory access at offset \[0-9\]+ overflows this variable.*(\n|\r\n|\r)" } */ /* { dg-output "\[^\n\r]*in main.*stack-overflow-1.c.*(\n|\r\n|\r)" */ diff --git a/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c b/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c new file mode 100644 index 000..8a4fef2be32 --- /dev/null +++ b/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-1.c @@ -0,0 +1,27 @@ +/* { dg-do run } */ +/* { dg-shouldfail "asan" } */ + +#define STACK_ALLOC_SIZE 8 +#define READ_SIZE 8 +#define OFFSET 1 + +struct X { + char bytes[READ_SIZE]; +}; + +__attribute__((noinline)) struct X out_of_bounds() { + volatile char bytes[STACK_ALLOC_SIZE]; + struct X* x_ptr = (struct X*)(bytes + OFFSET); + return *x_ptr; +} + +int main() { + struct X x = out_of_bounds(); + return x.bytes[0]; +} + +/* { dg-output "ERROR: AddressSanitizer:? stack-buffer-overflow on address\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "READ of size 8 at 0x\[0-9a-f\]+ thread T0\[^\n\r]*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*Address 0x\[0-9a-f\]+ is located in stack of thread T0.*(\n|\r\n|\r)" */ +/* { dg-output ".*Memory access at offset \[0-9\]+ partially overflows this variable.*(\n|\r\n|\r)" } */ +/* { dg-output "\[^\n\r]*in out_of_bounds.*stack-overflow-multi-byte-partial-1.c.*(\n|\r\n|\r)" */ diff --git a/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-2.c b/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-2.c new file mode 100644 index 000..ddc86a6ddfc --- /dev/null +++ b/gcc/testsuite/c-c++-common/asan/stack-overflow-multi-byte-partial-2.c @@ -0,0 +1,27 @@ +/* { dg-do run } */ +/*