From ebde4a5f013a4b8f2550b751b0fa1342a0c0b6a8 Mon Sep 17 00:00:00 2001 From: Wern Lim Date: Thu, 26 Jun 2025 12:11:53 +0800 Subject: [PATCH 1/3] [asan] Fix and report the mem access start addr instead of poisoned addr ACCESS_MEMORY_RANGE defined in asan_interceptors_memintrinsics.h reports the poisoned address (__bad), instead of the start address (__offset) during a memory access to ReportGenericError. We can determine that the latter (__offset) is the intended interpretation, as most error descriptions are decided by treating the given address as a start address (for example, see: PrintAccessAndVarIntersection in asan_descriptions.cpp, which decides whether a variable underflows or overflows depending on the given addr and access_size). GCC also uses the latter interpretation. For instance, in buffer overflows, it appears to do its own processing, and will report the start address of an overflowing read to ASan. This is in contrast to Clang, which uses __asan_memcpy directly. This patch fixes the above issue. Existing tests previously assumed and check for the former incorrect behaviour. The error descriptions in those tests have thus been corrected. --- compiler-rt/lib/asan/asan_interceptors_memintrinsics.h | 2 +- ...erflow-large.cpp => heap-overflow-large-offset.cpp} | 0 .../{wild_pointer.cpp => heap-overflow-large-read.cpp} | 10 +++++----- compiler-rt/test/asan/TestCases/strcasestr-1.c | 2 +- compiler-rt/test/asan/TestCases/strcasestr-2.c | 2 +- compiler-rt/test/asan/TestCases/strcspn-1.c | 2 +- compiler-rt/test/asan/TestCases/strcspn-2.c | 2 +- compiler-rt/test/asan/TestCases/strpbrk-1.c | 2 +- compiler-rt/test/asan/TestCases/strpbrk-2.c | 2 +- compiler-rt/test/asan/TestCases/strspn-1.c | 2 +- compiler-rt/test/asan/TestCases/strspn-2.c | 2 +- compiler-rt/test/asan/TestCases/strstr-1.c | 2 +- compiler-rt/test/asan/TestCases/strstr-2.c | 2 +- compiler-rt/test/asan/TestCases/strtok.c | 8 ++++---- 14 files changed, 20 insertions(+), 20 deletions(-) rename compiler-rt/test/asan/TestCases/{heap-overflow-large.cpp => heap-overflow-large-offset.cpp} (100%) rename compiler-rt/test/asan/TestCases/{wild_pointer.cpp => heap-overflow-large-read.cpp} (71%) diff --git a/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h b/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h index 14727a5d665ed..c734b46e5e21e 100644 --- a/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h +++ b/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h @@ -74,7 +74,7 @@ struct AsanInterceptorContext { } \ if (!suppressed) { \ GET_CURRENT_PC_BP_SP; \ - ReportGenericError(pc, bp, sp, __bad, isWrite, __size, 0, false); \ + ReportGenericError(pc, bp, sp, __offset, isWrite, __size, 0, false); \ } \ } \ } while (0) diff --git a/compiler-rt/test/asan/TestCases/heap-overflow-large.cpp b/compiler-rt/test/asan/TestCases/heap-overflow-large-offset.cpp similarity index 100% rename from compiler-rt/test/asan/TestCases/heap-overflow-large.cpp rename to compiler-rt/test/asan/TestCases/heap-overflow-large-offset.cpp diff --git a/compiler-rt/test/asan/TestCases/wild_pointer.cpp b/compiler-rt/test/asan/TestCases/heap-overflow-large-read.cpp similarity index 71% rename from compiler-rt/test/asan/TestCases/wild_pointer.cpp rename to compiler-rt/test/asan/TestCases/heap-overflow-large-read.cpp index 8969a285e565e..7671e93d74b0c 100644 --- a/compiler-rt/test/asan/TestCases/wild_pointer.cpp +++ b/compiler-rt/test/asan/TestCases/heap-overflow-large-read.cpp @@ -11,7 +11,7 @@ int main() { char *p = new char; char *dest = new char; - const size_t offset = 0x4567890123456789; + const size_t size = 0x4567890123456789; // The output here needs to match the output from the sanitizer runtime, // which includes 0x and prints hex in lower case. @@ -19,14 +19,14 @@ int main() { // On Windows, %p omits %0x and prints hex characters in upper case, // so we use PRIxPTR instead of %p. fprintf(stderr, "Expected bad addr: %#" PRIxPTR "\n", - reinterpret_cast(p + offset)); + reinterpret_cast(p)); // Flush it so the output came out before the asan report. fflush(stderr); - memmove(dest, p, offset); + memmove(dest, p, size); return 0; } // CHECK: Expected bad addr: [[ADDR:0x[0-9,a-f]+]] -// CHECK: AddressSanitizer: unknown-crash on address [[ADDR]] -// CHECK: Address [[ADDR]] is a wild pointer inside of access range of size 0x4567890123456789 +// CHECK: AddressSanitizer: heap-buffer-overflow on address [[ADDR]] +// CHECK: READ of size 5001116549197948809 at [[ADDR]] thread T0 diff --git a/compiler-rt/test/asan/TestCases/strcasestr-1.c b/compiler-rt/test/asan/TestCases/strcasestr-1.c index 9fa4a2b31b97f..a3118f0f6a022 100644 --- a/compiler-rt/test/asan/TestCases/strcasestr-1.c +++ b/compiler-rt/test/asan/TestCases/strcasestr-1.c @@ -19,7 +19,7 @@ int main(int argc, char **argv) { char s1[4] = "abC"; __asan_poison_memory_region ((char *)&s1[2], 2); r = strcasestr(s1, s2); - // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == s1 + 2); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strcasestr-2.c b/compiler-rt/test/asan/TestCases/strcasestr-2.c index 920d11e275c6a..551c081669ea0 100644 --- a/compiler-rt/test/asan/TestCases/strcasestr-2.c +++ b/compiler-rt/test/asan/TestCases/strcasestr-2.c @@ -20,6 +20,6 @@ int main(int argc, char **argv) { __asan_poison_memory_region ((char *)&s2[2], 2); r = strcasestr(s1, s2); assert(r == 0); - // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable return 0; } diff --git a/compiler-rt/test/asan/TestCases/strcspn-1.c b/compiler-rt/test/asan/TestCases/strcspn-1.c index 2a9f7d7fbddfc..f7a7722d5ec0c 100644 --- a/compiler-rt/test/asan/TestCases/strcspn-1.c +++ b/compiler-rt/test/asan/TestCases/strcspn-1.c @@ -14,7 +14,7 @@ int main(int argc, char **argv) { char s1[4] = "caB"; __asan_poison_memory_region ((char *)&s1[2], 2); r = strcspn(s1, s2); - // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == 1); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strcspn-2.c b/compiler-rt/test/asan/TestCases/strcspn-2.c index a51fb911e8025..338da85b8d496 100644 --- a/compiler-rt/test/asan/TestCases/strcspn-2.c +++ b/compiler-rt/test/asan/TestCases/strcspn-2.c @@ -14,7 +14,7 @@ int main(int argc, char **argv) { char s2[4] = "abc"; __asan_poison_memory_region ((char *)&s2[2], 2); r = strcspn(s1, s2); - // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == 0); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strpbrk-1.c b/compiler-rt/test/asan/TestCases/strpbrk-1.c index eb323267828bb..438d4ff288463 100644 --- a/compiler-rt/test/asan/TestCases/strpbrk-1.c +++ b/compiler-rt/test/asan/TestCases/strpbrk-1.c @@ -14,7 +14,7 @@ int main(int argc, char **argv) { char s1[4] = "cab"; __asan_poison_memory_region ((char *)&s1[2], 2); r = strpbrk(s1, s2); - // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == s1 + 1); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strpbrk-2.c b/compiler-rt/test/asan/TestCases/strpbrk-2.c index 1f24656dcb58d..46948b086835a 100644 --- a/compiler-rt/test/asan/TestCases/strpbrk-2.c +++ b/compiler-rt/test/asan/TestCases/strpbrk-2.c @@ -14,7 +14,7 @@ int main(int argc, char **argv) { char s2[4] = "bca"; __asan_poison_memory_region ((char *)&s2[2], 2); r = strpbrk(s1, s2); - // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == s1); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strspn-1.c b/compiler-rt/test/asan/TestCases/strspn-1.c index 5ddb14f507ff5..4265b3df66ec6 100644 --- a/compiler-rt/test/asan/TestCases/strspn-1.c +++ b/compiler-rt/test/asan/TestCases/strspn-1.c @@ -14,7 +14,7 @@ int main(int argc, char **argv) { char s1[4] = "acb"; __asan_poison_memory_region ((char *)&s1[2], 2); r = strspn(s1, s2); - // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == 1); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strspn-2.c b/compiler-rt/test/asan/TestCases/strspn-2.c index d564ef8aefb93..098b66dd3ea48 100644 --- a/compiler-rt/test/asan/TestCases/strspn-2.c +++ b/compiler-rt/test/asan/TestCases/strspn-2.c @@ -14,7 +14,7 @@ int main(int argc, char **argv) { char s2[5] = "abcd"; __asan_poison_memory_region ((char *)&s2[3], 2); r = strspn(s1, s2); - // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r >= 2); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strstr-1.c b/compiler-rt/test/asan/TestCases/strstr-1.c index 319cff546d193..23ece04000fc2 100644 --- a/compiler-rt/test/asan/TestCases/strstr-1.c +++ b/compiler-rt/test/asan/TestCases/strstr-1.c @@ -15,7 +15,7 @@ int main(int argc, char **argv) { char s1[4] = "acb"; __asan_poison_memory_region ((char *)&s1[2], 2); r = strstr(s1, s2); - // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} {{partially overflows this variable|is inside this variable}} + // CHECK:'s1'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == s1 + 1); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strstr-2.c b/compiler-rt/test/asan/TestCases/strstr-2.c index 4d00c6e632bbb..caa4543fec07f 100644 --- a/compiler-rt/test/asan/TestCases/strstr-2.c +++ b/compiler-rt/test/asan/TestCases/strstr-2.c @@ -15,7 +15,7 @@ int main(int argc, char **argv) { char s2[4] = "cab"; __asan_poison_memory_region ((char *)&s2[2], 2); r = strstr(s1, s2); - // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK:'s2'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable assert(r == 0); return 0; } diff --git a/compiler-rt/test/asan/TestCases/strtok.c b/compiler-rt/test/asan/TestCases/strtok.c index c7b2617772544..ea92d611e8b1a 100644 --- a/compiler-rt/test/asan/TestCases/strtok.c +++ b/compiler-rt/test/asan/TestCases/strtok.c @@ -35,7 +35,7 @@ void test1() { char token_delimiter[2] = "b"; __asan_poison_memory_region ((char *)&token_delimiter[1], 2); token = strtok(s, token_delimiter); - // CHECK1: 'token_delimiter'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK1: 'token_delimiter'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable } // Check that we find overflows in the delimiters on the second call (str == NULL) @@ -48,7 +48,7 @@ void test2() { assert(strcmp(token, "a") == 0); __asan_poison_memory_region ((char *)&token_delimiter[1], 2); token = strtok(NULL, token_delimiter); - // CHECK2: 'token_delimiter'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK2: 'token_delimiter'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable } // Check that we find overflows in the string (only on the first call) with strict_string_checks. @@ -58,7 +58,7 @@ void test3() { char token_delimiter[2] = "b"; __asan_poison_memory_region ((char *)&s[3], 2); token = strtok(s, token_delimiter); - // CHECK3: 's'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK3: 's'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable } // Check that we do not crash when strtok returns NULL with strict_string_checks. @@ -78,7 +78,7 @@ void test5() { __asan_poison_memory_region ((char *)&s[2], 2); __asan_poison_memory_region ((char *)&token_delimiter[1], 2); token = strtok(s, token_delimiter); - // CHECK5: 's'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable + // CHECK5: 's'{{.*}} <== Memory access at offset {{[0-9]+}} is inside this variable } // Check that we find overflows in the delimiters (only on the first call) with !strict_string_checks. From 40515e31aaae13665a3d326d7bd431ed9651d1ed Mon Sep 17 00:00:00 2001 From: Wern Lim Date: Tue, 17 Jun 2025 14:52:28 +0800 Subject: [PATCH 2/3] [asan] Fix 'unknown-crash' reported for multi-byte errors Given that a reported error by ASan spans multiple bytes, ASan may flag the error as an 'unknown-crash' instead of the appropriate error name. This error can be reproduced via a partial buffer overflow (any GCC, or after performing the patch in the previous commit to Clang). They'll report 'unknown-crash' instead of 'stack-buffer-overflow' for the below: # minimal reprod # https://godbolt.org/z/abrjrvnzj # # gcc -fsanitize=address reprod.c struct X { char bytes[16]; }; __attribute__((noinline)) struct X out_of_bounds() { volatile char bytes[16]; struct X* x_ptr = (struct X*)(bytes + 2); return *x_ptr; } int main() { struct X x = out_of_bounds(); return x.bytes[0]; } This is due to a flawed heuristic in asan_errors.cpp, which won't always locate the appropriate shadow byte that would indicate a corresponding error. This can happen for any reported errors which span either: exactly 8 bytes, or 16 and more bytes. This bug was previously hidden from Clang (but has always been present in GCC) until the previous commit's fix on address reporting. Specifically, ACCESS_MEMORY_RANGE in ASan previously reports the first poisoned byte (instead of the start address, like in GCC). This masked the above bug from occuring, as it coincidentally guarantees the heuristic will always work, with slightly inaccurate reports. 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). --- compiler-rt/lib/asan/asan_errors.cpp | 12 ++++- .../stack-buffer-overflow-partial-1.cpp | 45 ++++++++++++++++++ .../stack-buffer-overflow-partial-2.cpp | 47 +++++++++++++++++++ .../stack-buffer-overflow-partial-3.cpp | 47 +++++++++++++++++++ .../stack-buffer-overflow-partial-4.cpp | 47 +++++++++++++++++++ 5 files changed, 196 insertions(+), 2 deletions(-) create mode 100644 compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-1.cpp create mode 100644 compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-2.cpp create mode 100644 compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-3.cpp create mode 100644 compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-4.cpp diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp index 2a207cd06ccac..075efec081b53 100644 --- a/compiler-rt/lib/asan/asan_errors.cpp +++ b/compiler-rt/lib/asan/asan_errors.cpp @@ -437,8 +437,16 @@ ErrorGeneric::ErrorGeneric(u32 tid, uptr pc_, uptr bp_, uptr sp_, uptr addr, bug_descr = "unknown-crash"; if (AddrIsInMem(addr)) { u8 *shadow_addr = (u8 *)MemToShadow(addr); - // If we are accessing 16 bytes, look at the second shadow byte. - if (*shadow_addr == 0 && access_size > ASAN_SHADOW_GRANULARITY) + u8 *shadow_addr_upper_bound = (u8 *)MEM_TO_SHADOW(addr + access_size); + // We use the MEM_TO_SHADOW macro for the upper bound above instead of + // MemToShadow to skip the assertion that (addr + access_size) is within + // the valid memory range. The validity of the shadow address is checked + // via AddrIsInShadow in the while loop below. + + // If the access could span multiple shadow bytes, + // do a sequential scan and look for the first bad shadow byte. + while (*shadow_addr == 0 && shadow_addr < shadow_addr_upper_bound && + AddrIsInShadow((uptr)(shadow_addr + 1))) shadow_addr++; // If we are in the partial right redzone, look at the next shadow byte. if (*shadow_addr > 0 && *shadow_addr < 128) shadow_addr++; diff --git a/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-1.cpp b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-1.cpp new file mode 100644 index 0000000000000..9f3358fdf4865 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-1.cpp @@ -0,0 +1,45 @@ +// RUN: %clangxx_asan %s -o %t +// RUN: not %run %t 1 2>&1 | FileCheck %s +// RUN: not %run %t 2 2>&1 | FileCheck %s +// RUN: not %run %t 7 2>&1 | FileCheck %s + +#define STACK_ALLOC_SIZE 8 +#define READ_SIZE 8 + +#include +#include + +struct X { + char bytes[READ_SIZE]; +}; + +__attribute__((noinline)) struct X out_of_bounds(int offset) { + volatile char bytes[STACK_ALLOC_SIZE]; + struct X* x_ptr = (struct X*)(bytes + offset); + return *x_ptr; +} + +int main(int argc, char **argv) { + int offset = atoi(argv[1]); + + // We are explicitly testing that we correctly detect and report this error + // as a *partial stack buffer overflow*. + assert(offset < STACK_ALLOC_SIZE); + assert(offset + READ_SIZE > STACK_ALLOC_SIZE); + + struct X x = out_of_bounds(offset); + int y = 0; + + for (int i = 0; i < READ_SIZE; i++) { + y ^= x.bytes[i]; + } + + return y; +} + +// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address +// CHECK: {{READ of size 8 at 0x.* thread T0}} +// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-1.cpp:}} +// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}} +// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-1.cpp:}} +// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable \ No newline at end of file diff --git a/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-2.cpp b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-2.cpp new file mode 100644 index 0000000000000..fa1a4131bc51e --- /dev/null +++ b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-2.cpp @@ -0,0 +1,47 @@ +// RUN: %clangxx_asan %s -o %t +// RUN: not %run %t 1 2>&1 | FileCheck %s +// RUN: not %run %t 2 2>&1 | FileCheck %s +// RUN: not %run %t 7 2>&1 | FileCheck %s +// RUN: not %run %t 8 2>&1 | FileCheck %s +// RUN: not %run %t 15 2>&1 | FileCheck %s + +#define STACK_ALLOC_SIZE 16 +#define READ_SIZE 16 + +#include +#include + +struct X { + char bytes[READ_SIZE]; +}; + +__attribute__((noinline)) struct X out_of_bounds(int offset) { + volatile char bytes[STACK_ALLOC_SIZE]; + struct X* x_ptr = (struct X*)(bytes + offset); + return *x_ptr; +} + +int main(int argc, char **argv) { + int offset = atoi(argv[1]); + + // We are explicitly testing that we correctly detect and report this error + // as a *partial stack buffer overflow*. + assert(offset < STACK_ALLOC_SIZE); + assert(offset + READ_SIZE > STACK_ALLOC_SIZE); + + struct X x = out_of_bounds(offset); + int y = 0; + + for (int i = 0; i < READ_SIZE; i++) { + y ^= x.bytes[i]; + } + + return y; +} + +// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address +// CHECK: {{READ of size 16 at 0x.* thread T0}} +// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-2.cpp:}} +// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}} +// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-2.cpp:}} +// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable \ No newline at end of file diff --git a/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-3.cpp b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-3.cpp new file mode 100644 index 0000000000000..3bb628e8373f7 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-3.cpp @@ -0,0 +1,47 @@ +// RUN: %clangxx_asan %s -o %t +// RUN: not %run %t 2 2>&1 | FileCheck %s +// RUN: not %run %t 3 2>&1 | FileCheck %s +// RUN: not %run %t 7 2>&1 | FileCheck %s +// RUN: not %run %t 8 2>&1 | FileCheck %s +// RUN: not %run %t 15 2>&1 | FileCheck %s + +#define STACK_ALLOC_SIZE 16 +#define READ_SIZE 15 + +#include +#include + +struct X { + char bytes[READ_SIZE]; +}; + +__attribute__((noinline)) struct X out_of_bounds(int offset) { + volatile char bytes[STACK_ALLOC_SIZE]; + struct X* x_ptr = (struct X*)(bytes + offset); + return *x_ptr; +} + +int main(int argc, char **argv) { + int offset = atoi(argv[1]); + + // We are explicitly testing that we correctly detect and report this error + // as a *partial stack buffer overflow*. + assert(offset < STACK_ALLOC_SIZE); + assert(offset + READ_SIZE > STACK_ALLOC_SIZE); + + struct X x = out_of_bounds(offset); + int y = 0; + + for (int i = 0; i < READ_SIZE; i++) { + y ^= x.bytes[i]; + } + + return y; +} + +// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address +// CHECK: {{READ of size 15 at 0x.* thread T0}} +// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-3.cpp:}} +// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}} +// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-3.cpp:}} +// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable \ No newline at end of file diff --git a/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-4.cpp b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-4.cpp new file mode 100644 index 0000000000000..e31096ee1d486 --- /dev/null +++ b/compiler-rt/test/asan/TestCases/stack-buffer-overflow-partial-4.cpp @@ -0,0 +1,47 @@ +// RUN: %clangxx_asan %s -o %t +// RUN: not %run %t 3 2>&1 | FileCheck %s +// RUN: not %run %t 7 2>&1 | FileCheck %s +// RUN: not %run %t 10 2>&1 | FileCheck %s +// RUN: not %run %t 13 2>&1 | FileCheck %s +// RUN: not %run %t 19 2>&1 | FileCheck %s + +#define STACK_ALLOC_SIZE 20 +#define READ_SIZE 18 + +#include +#include + +struct X { + char bytes[READ_SIZE]; +}; + +__attribute__((noinline)) struct X out_of_bounds(int offset) { + volatile char bytes[STACK_ALLOC_SIZE]; + struct X* x_ptr = (struct X*)(bytes + offset); + return *x_ptr; +} + +int main(int argc, char **argv) { + int offset = atoi(argv[1]); + + // We are explicitly testing that we correctly detect and report this error + // as a *partial stack buffer overflow*. + assert(offset < STACK_ALLOC_SIZE); + assert(offset + READ_SIZE > STACK_ALLOC_SIZE); + + struct X x = out_of_bounds(offset); + int y = 0; + + for (int i = 0; i < READ_SIZE; i++) { + y ^= x.bytes[i]; + } + + return y; +} + +// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address +// CHECK: {{READ of size 18 at 0x.* thread T0}} +// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-4.cpp:}} +// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}} +// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-4.cpp:}} +// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable \ No newline at end of file From b4754b8d0c63e578921983a9c7d626d28ce27c76 Mon Sep 17 00:00:00 2001 From: Wern Lim Date: Thu, 26 Jun 2025 16:55:18 +0800 Subject: [PATCH 3/3] [asan] Reformat spacing --- .../asan/asan_interceptors_memintrinsics.h | 48 +++++++++---------- 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h b/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h index c734b46e5e21e..7bed604aec678 100644 --- a/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h +++ b/compiler-rt/lib/asan/asan_interceptors_memintrinsics.h @@ -52,31 +52,31 @@ struct AsanInterceptorContext { // that no extra frames are created, and stack trace contains // relevant information only. // We check all shadow bytes. -#define ACCESS_MEMORY_RANGE(ctx, offset, size, isWrite) \ - do { \ - uptr __offset = (uptr)(offset); \ - uptr __size = (uptr)(size); \ - uptr __bad = 0; \ - if (UNLIKELY(__offset > __offset + __size)) { \ - GET_STACK_TRACE_FATAL_HERE; \ - ReportStringFunctionSizeOverflow(__offset, __size, &stack); \ - } \ - if (UNLIKELY(!QuickCheckForUnpoisonedRegion(__offset, __size)) && \ - (__bad = __asan_region_is_poisoned(__offset, __size))) { \ - AsanInterceptorContext *_ctx = (AsanInterceptorContext *)ctx; \ - bool suppressed = false; \ - if (_ctx) { \ - suppressed = IsInterceptorSuppressed(_ctx->interceptor_name); \ - if (!suppressed && HaveStackTraceBasedSuppressions()) { \ - GET_STACK_TRACE_FATAL_HERE; \ - suppressed = IsStackTraceSuppressed(&stack); \ - } \ - } \ - if (!suppressed) { \ - GET_CURRENT_PC_BP_SP; \ +#define ACCESS_MEMORY_RANGE(ctx, offset, size, isWrite) \ + do { \ + uptr __offset = (uptr)(offset); \ + uptr __size = (uptr)(size); \ + uptr __bad = 0; \ + if (UNLIKELY(__offset > __offset + __size)) { \ + GET_STACK_TRACE_FATAL_HERE; \ + ReportStringFunctionSizeOverflow(__offset, __size, &stack); \ + } \ + if (UNLIKELY(!QuickCheckForUnpoisonedRegion(__offset, __size)) && \ + (__bad = __asan_region_is_poisoned(__offset, __size))) { \ + AsanInterceptorContext *_ctx = (AsanInterceptorContext *)ctx; \ + bool suppressed = false; \ + if (_ctx) { \ + suppressed = IsInterceptorSuppressed(_ctx->interceptor_name); \ + if (!suppressed && HaveStackTraceBasedSuppressions()) { \ + GET_STACK_TRACE_FATAL_HERE; \ + suppressed = IsStackTraceSuppressed(&stack); \ + } \ + } \ + if (!suppressed) { \ + GET_CURRENT_PC_BP_SP; \ ReportGenericError(pc, bp, sp, __offset, isWrite, __size, 0, false); \ - } \ - } \ + } \ + } \ } while (0) #define ASAN_READ_RANGE(ctx, offset, size) \