Skip to content

Commit 63f33c6

Browse files
committed
[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). 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. 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).
1 parent 4c1b214 commit 63f33c6

File tree

5 files changed

+196
-2
lines changed

5 files changed

+196
-2
lines changed

compiler-rt/lib/asan/asan_errors.cpp

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,8 +437,16 @@ ErrorGeneric::ErrorGeneric(u32 tid, uptr pc_, uptr bp_, uptr sp_, uptr addr,
437437
bug_descr = "unknown-crash";
438438
if (AddrIsInMem(addr)) {
439439
u8 *shadow_addr = (u8 *)MemToShadow(addr);
440-
// If we are accessing 16 bytes, look at the second shadow byte.
441-
if (*shadow_addr == 0 && access_size > ASAN_SHADOW_GRANULARITY)
440+
u8 *shadow_addr_upper_bound = (u8 *)MEM_TO_SHADOW(addr + access_size);
441+
// We use the MEM_TO_SHADOW macro for the upper bound above instead of
442+
// MemToShadow to skip the assertion that (addr + access_size) is within
443+
// the valid memory range. The validity of the shadow address is checked
444+
// via AddrIsInShadow in the while loop below.
445+
446+
// If the access could span multiple shadow bytes,
447+
// do a sequential scan and look for the first bad shadow byte.
448+
while (*shadow_addr == 0 && shadow_addr < shadow_addr_upper_bound &&
449+
AddrIsInShadow((uptr)(shadow_addr + 1)))
442450
shadow_addr++;
443451
// If we are in the partial right redzone, look at the next shadow byte.
444452
if (*shadow_addr > 0 && *shadow_addr < 128) shadow_addr++;
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
// RUN: %clangxx_asan %s -o %t
2+
// RUN: not %run %t 1 2>&1 | FileCheck %s
3+
// RUN: not %run %t 2 2>&1 | FileCheck %s
4+
// RUN: not %run %t 7 2>&1 | FileCheck %s
5+
6+
#define STACK_ALLOC_SIZE 8
7+
#define READ_SIZE 8
8+
9+
#include <stdlib.h>
10+
#include <assert.h>
11+
12+
struct X {
13+
char bytes[READ_SIZE];
14+
};
15+
16+
__attribute__((noinline)) struct X out_of_bounds(int offset) {
17+
volatile char bytes[STACK_ALLOC_SIZE];
18+
struct X* x_ptr = (struct X*)(bytes + offset);
19+
return *x_ptr;
20+
}
21+
22+
int main(int argc, char **argv) {
23+
int offset = atoi(argv[1]);
24+
25+
// We are explicitly testing that we correctly detect and report this error
26+
// as a *partial stack buffer overflow*.
27+
assert(offset < STACK_ALLOC_SIZE);
28+
assert(offset + READ_SIZE > STACK_ALLOC_SIZE);
29+
30+
struct X x = out_of_bounds(offset);
31+
int y = 0;
32+
33+
for (int i = 0; i < READ_SIZE; i++) {
34+
y ^= x.bytes[i];
35+
}
36+
37+
return y;
38+
}
39+
40+
// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address
41+
// CHECK: {{READ of size 8 at 0x.* thread T0}}
42+
// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-1.cpp:}}
43+
// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}}
44+
// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-1.cpp:}}
45+
// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %clangxx_asan %s -o %t
2+
// RUN: not %run %t 1 2>&1 | FileCheck %s
3+
// RUN: not %run %t 2 2>&1 | FileCheck %s
4+
// RUN: not %run %t 7 2>&1 | FileCheck %s
5+
// RUN: not %run %t 8 2>&1 | FileCheck %s
6+
// RUN: not %run %t 15 2>&1 | FileCheck %s
7+
8+
#define STACK_ALLOC_SIZE 16
9+
#define READ_SIZE 16
10+
11+
#include <stdlib.h>
12+
#include <assert.h>
13+
14+
struct X {
15+
char bytes[READ_SIZE];
16+
};
17+
18+
__attribute__((noinline)) struct X out_of_bounds(int offset) {
19+
volatile char bytes[STACK_ALLOC_SIZE];
20+
struct X* x_ptr = (struct X*)(bytes + offset);
21+
return *x_ptr;
22+
}
23+
24+
int main(int argc, char **argv) {
25+
int offset = atoi(argv[1]);
26+
27+
// We are explicitly testing that we correctly detect and report this error
28+
// as a *partial stack buffer overflow*.
29+
assert(offset < STACK_ALLOC_SIZE);
30+
assert(offset + READ_SIZE > STACK_ALLOC_SIZE);
31+
32+
struct X x = out_of_bounds(offset);
33+
int y = 0;
34+
35+
for (int i = 0; i < READ_SIZE; i++) {
36+
y ^= x.bytes[i];
37+
}
38+
39+
return y;
40+
}
41+
42+
// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address
43+
// CHECK: {{READ of size 16 at 0x.* thread T0}}
44+
// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-2.cpp:}}
45+
// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}}
46+
// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-2.cpp:}}
47+
// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %clangxx_asan %s -o %t
2+
// RUN: not %run %t 2 2>&1 | FileCheck %s
3+
// RUN: not %run %t 3 2>&1 | FileCheck %s
4+
// RUN: not %run %t 7 2>&1 | FileCheck %s
5+
// RUN: not %run %t 8 2>&1 | FileCheck %s
6+
// RUN: not %run %t 15 2>&1 | FileCheck %s
7+
8+
#define STACK_ALLOC_SIZE 16
9+
#define READ_SIZE 15
10+
11+
#include <stdlib.h>
12+
#include <assert.h>
13+
14+
struct X {
15+
char bytes[READ_SIZE];
16+
};
17+
18+
__attribute__((noinline)) struct X out_of_bounds(int offset) {
19+
volatile char bytes[STACK_ALLOC_SIZE];
20+
struct X* x_ptr = (struct X*)(bytes + offset);
21+
return *x_ptr;
22+
}
23+
24+
int main(int argc, char **argv) {
25+
int offset = atoi(argv[1]);
26+
27+
// We are explicitly testing that we correctly detect and report this error
28+
// as a *partial stack buffer overflow*.
29+
assert(offset < STACK_ALLOC_SIZE);
30+
assert(offset + READ_SIZE > STACK_ALLOC_SIZE);
31+
32+
struct X x = out_of_bounds(offset);
33+
int y = 0;
34+
35+
for (int i = 0; i < READ_SIZE; i++) {
36+
y ^= x.bytes[i];
37+
}
38+
39+
return y;
40+
}
41+
42+
// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address
43+
// CHECK: {{READ of size 15 at 0x.* thread T0}}
44+
// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-3.cpp:}}
45+
// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}}
46+
// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-3.cpp:}}
47+
// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// RUN: %clangxx_asan %s -o %t
2+
// RUN: not %run %t 3 2>&1 | FileCheck %s
3+
// RUN: not %run %t 7 2>&1 | FileCheck %s
4+
// RUN: not %run %t 10 2>&1 | FileCheck %s
5+
// RUN: not %run %t 13 2>&1 | FileCheck %s
6+
// RUN: not %run %t 19 2>&1 | FileCheck %s
7+
8+
#define STACK_ALLOC_SIZE 20
9+
#define READ_SIZE 18
10+
11+
#include <stdlib.h>
12+
#include <assert.h>
13+
14+
struct X {
15+
char bytes[READ_SIZE];
16+
};
17+
18+
__attribute__((noinline)) struct X out_of_bounds(int offset) {
19+
volatile char bytes[STACK_ALLOC_SIZE];
20+
struct X* x_ptr = (struct X*)(bytes + offset);
21+
return *x_ptr;
22+
}
23+
24+
int main(int argc, char **argv) {
25+
int offset = atoi(argv[1]);
26+
27+
// We are explicitly testing that we correctly detect and report this error
28+
// as a *partial stack buffer overflow*.
29+
assert(offset < STACK_ALLOC_SIZE);
30+
assert(offset + READ_SIZE > STACK_ALLOC_SIZE);
31+
32+
struct X x = out_of_bounds(offset);
33+
int y = 0;
34+
35+
for (int i = 0; i < READ_SIZE; i++) {
36+
y ^= x.bytes[i];
37+
}
38+
39+
return y;
40+
}
41+
42+
// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow on address
43+
// CHECK: {{READ of size 18 at 0x.* thread T0}}
44+
// CHECK: {{.* 0x.* in out_of_bounds.*stack-buffer-overflow-partial-4.cpp:}}
45+
// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}}
46+
// CHECK: {{ #0 0x.* in out_of_bounds.*stack-buffer-overflow-partial-4.cpp:}}
47+
// CHECK: 'bytes'{{.*}} <== Memory access at offset {{[0-9]+}} partially overflows this variable

0 commit comments

Comments
 (0)