[compiler-rt] [asan] Fix `unknown-crash` reported for multi-byte errors and incorrect addresses (PR #144480)
via llvm-commits
llvm-commits at lists.llvm.org
Thu Jun 26 02:09:12 PDT 2025
https://github.com/wxwern updated https://github.com/llvm/llvm-project/pull/144480
>From 43e577eaf1b3e9f45cdcda9bd9c31d805e47245c Mon Sep 17 00:00:00 2001
From: Wern Lim <welim at drwsg.com>
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<uintptr_t>(p + offset));
+ reinterpret_cast<uintptr_t>(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 4cda9d996f3b47c4d364c8b9d81fbff23ca7d018 Mon Sep 17 00:00:00 2001
From: Wern Lim <welim at drwsg.com>
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 addresses. Specifically,
the fact that Clang previously reported the first poisoned byte
(instead of the start address, like in GCC) masked this bug from
occuring, as it coincidentally guarantees the heuristic will always
work.
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 <stdlib.h>
+#include <assert.h>
+
+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 <stdlib.h>
+#include <assert.h>
+
+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 <stdlib.h>
+#include <assert.h>
+
+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 <stdlib.h>
+#include <assert.h>
+
+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 fec87fc2540dd77190c9cbab08d720ebb856ad5f Mon Sep 17 00:00:00 2001
From: Wern Lim <welim at drwsg.com>
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) \
More information about the llvm-commits
mailing list