[compiler-rt] [asan] Fix `unknown-crash` reported for multi-byte errors (PR #144480)

via llvm-commits llvm-commits at lists.llvm.org
Thu Jun 26 01:18:31 PDT 2025


https://github.com/wxwern updated https://github.com/llvm/llvm-project/pull/144480

>From f39a0fed00ce4b252aecd517e98c7b4c7ceac8a7 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 1/4] [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 (on gcc),
which reports 'unknown-crash' instead of 'stack-buffer-overflow' for
the below:

    # minimal reprod (should occur on gcc-7 - gcc-15)
    # 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.

The above example doesn't reproduce the issue on clang as it reports
errors via different pathways:

 - gcc-compiled binaries report the starting address and size of the
   failing read attempt to asan.

 - clang-compiled binaries highlight the first byte access that
   overflows the buffer to asan. Note: out-of-scope, but this is also
   possibly misleading, as it still reports the full size of the read
   attempt, paired with an address that's not the start of the read.

This behavior appears to be identical for all past versions tested.
I'm not aware of a way to replicate this specific issue with clang,
though it might have impacted error reporting in other areas.

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 | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)

diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index 2a207cd06ccac..626b0f294e110 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -437,8 +437,10 @@ 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 *)MemToShadow(addr + access_size);
+      // 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)
         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++;

>From 8b76a1a9d3f804ef906cdd501812d4d954207dbe Mon Sep 17 00:00:00 2001
From: Wern Lim <welim at drwsg.com>
Date: Thu, 26 Jun 2025 11:53:15 +0800
Subject: [PATCH 2/4] [asan] fix potential overflow when reading shadow addr

---
 compiler-rt/lib/asan/asan_errors.cpp | 10 ++++++++--
 1 file changed, 8 insertions(+), 2 deletions(-)

diff --git a/compiler-rt/lib/asan/asan_errors.cpp b/compiler-rt/lib/asan/asan_errors.cpp
index 626b0f294e110..075efec081b53 100644
--- a/compiler-rt/lib/asan/asan_errors.cpp
+++ b/compiler-rt/lib/asan/asan_errors.cpp
@@ -437,10 +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);
-      u8 *shadow_addr_upper_bound = (u8 *)MemToShadow(addr + access_size);
+      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)
+      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++;

>From 7d4d64d2ef8782efbbdb75be13feb7b270230df0 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 3/4] [asan] fix reported addresses not being the start of the
 read.

ACCESS_MEMORY_RANGE defined in asan_interceptors_memintrinsics.h
incorrectly reports the poisoned address (__bad) instead of
the memory access start address (__offset) to ReportGenericError.

We can determine that this is the case as most error descriptions
are computed by treating the given addr as a start address (e.g.,
see: PrintAccessAndVarIntersection in asan_descriptions.cpp).

This patch fixes the above issue.

Existing tests previously check for the incorrect behaviour. Their
corresponding expected error descriptions have been updated with
the correct error messages.
---
 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 8dd257b815d5f63e6f3ab89f05b1a3a16a9873a5 Mon Sep 17 00:00:00 2001
From: Wern Lim <welim at drwsg.com>
Date: Thu, 26 Jun 2025 16:15:42 +0800
Subject: [PATCH 4/4] [asan] add tests to validate error description reporting

These are regression tests for a bug resulting in gcc's asan
reporting 'unknown-crash', which is previously hidden from clang
due to another bug (fixed in the commit prior to this).
---
 .../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 +++++++++++++++++++
 4 files changed, 186 insertions(+)
 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/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



More information about the llvm-commits mailing list