Skip to content

Commit 50f3daa

Browse files
committed
[asan] Change the way we report the alloca frame on stack-buff-overflow.
Before: the function name was stored by the compiler as a constant string and the run-time was printing it. Now: the PC is stored instead and the run-time prints the full symbolized frame. This adds a couple of instructions into every function with non-empty stack frame, but also reduces the binary size because we store less strings (I saw 2% size reduction). This change bumps the asan ABI version to v3. compiler-rt part, llvm part will follow. Example of report (now): ==31711==ERROR: AddressSanitizer: stack-buffer-overflow on address 0x7fffa77cf1c5 at pc 0x41feb0 bp 0x7fffa77cefb0 sp 0x7fffa77cefa8 READ of size 1 at 0x7fffa77cf1c5 thread T0 #0 0x41feaf in Frame0(int, char*, char*, char*) stack-oob-frames.cc:20 #1 0x41f7ff in Frame1(int, char*, char*) stack-oob-frames.cc:24 #2 0x41f477 in Frame2(int, char*) stack-oob-frames.cc:28 #3 0x41f194 in Frame3(int) stack-oob-frames.cc:32 #4 0x41eee0 in main stack-oob-frames.cc:38 #5 0x7f0c5566f76c (/lib/x86_64-linux-gnu/libc.so.6+0x2176c) #6 0x41eb1c (/usr/local/google/kcc/llvm_cmake/a.out+0x41eb1c) Address 0x7fffa77cf1c5 is located in stack of thread T0 at offset 293 in frame #0 0x41f87f in Frame0(int, char*, char*, char*) stack-oob-frames.cc:12 <<<<<<<<<<<<<< this is new This frame has 6 object(s): [32, 36) 'frame.addr' [96, 104) 'a.addr' [160, 168) 'b.addr' [224, 232) 'c.addr' [288, 292) 's' [352, 360) 'd' git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@177723 91177308-0d34-0410-b5e6-96231b3b80d8
1 parent 6e5ff89 commit 50f3daa

10 files changed

+128
-48
lines changed

lib/asan/asan_interface_internal.h

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,13 @@ extern "C" {
2525
// Everytime the asan ABI changes we also change the version number in this
2626
// name. Objects build with incompatible asan ABI version
2727
// will not link with run-time.
28-
void __asan_init_v2() SANITIZER_INTERFACE_ATTRIBUTE;
29-
#define __asan_init __asan_init_v2
28+
// Changes between ABI versions:
29+
// v1=>v2: added 'module_name' to __asan_global
30+
// v2=>v3: stack frame description (created by the compiler)
31+
// contains the function PC as the 3-rd field (see
32+
// DescribeAddressIfStack).
33+
void __asan_init_v3() SANITIZER_INTERFACE_ATTRIBUTE;
34+
#define __asan_init __asan_init_v3
3035

3136
// This structure describes an instrumented global variable.
3237
struct __asan_global {

lib/asan/asan_report.cc

Lines changed: 41 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -235,33 +235,61 @@ bool DescribeAddressIfShadow(uptr addr) {
235235
return false;
236236
}
237237

238+
// Return " (thread_name) " or an empty string if the name is empty.
239+
const char *ThreadNameWithParenthesis(AsanThreadContext *t, char buff[],
240+
uptr buff_len) {
241+
const char *name = t->name;
242+
if (name[0] == '\0') return "";
243+
buff[0] = 0;
244+
internal_strncat(buff, " (", 3);
245+
internal_strncat(buff, name, buff_len - 4);
246+
internal_strncat(buff, ")", 2);
247+
return buff;
248+
}
249+
250+
const char *ThreadNameWithParenthesis(u32 tid, char buff[],
251+
uptr buff_len) {
252+
if (tid == kInvalidTid) return "";
253+
asanThreadRegistry().CheckLocked();
254+
AsanThreadContext *t = GetThreadContextByTidLocked(tid);
255+
return ThreadNameWithParenthesis(t, buff, buff_len);
256+
}
257+
238258
bool DescribeAddressIfStack(uptr addr, uptr access_size) {
239259
AsanThread *t = FindThreadByStackAddress(addr);
240260
if (!t) return false;
241261
const sptr kBufSize = 4095;
242262
char buf[kBufSize];
243263
uptr offset = 0;
244-
const char *frame_descr = t->GetFrameNameByAddr(addr, &offset);
264+
uptr frame_pc = 0;
265+
char tname[128];
266+
const char *frame_descr = t->GetFrameNameByAddr(addr, &offset, &frame_pc);
245267
// This string is created by the compiler and has the following form:
246-
// "FunctioName n alloc_1 alloc_2 ... alloc_n"
268+
// "n alloc_1 alloc_2 ... alloc_n"
247269
// where alloc_i looks like "offset size len ObjectName ".
248270
CHECK(frame_descr);
249-
// Report the function name and the offset.
250-
const char *name_end = internal_strchr(frame_descr, ' ');
251-
CHECK(name_end);
252-
buf[0] = 0;
253-
internal_strncat(buf, frame_descr,
254-
Min(kBufSize,
255-
static_cast<sptr>(name_end - frame_descr)));
256271
Decorator d;
257272
Printf("%s", d.Location());
258-
Printf("Address %p is located at offset %zu "
259-
"in frame <%s> of T%d's stack:\n",
260-
(void*)addr, offset, Demangle(buf), t->tid());
273+
Printf("Address %p is located in stack of thread T%d%s "
274+
"at offset %zu in frame\n",
275+
addr, t->tid(),
276+
ThreadNameWithParenthesis(t->tid(), tname, sizeof(tname)),
277+
offset);
278+
// Now we print the frame where the alloca has happened.
279+
// We print this frame as a stack trace with one element.
280+
// The symbolizer may print more than one frame if inlining was involved.
281+
// The frame numbers may be different than those in the stack trace printed
282+
// previously. That's unfortunate, but I have no better solution,
283+
// especially given that the alloca may be from entirely different place
284+
// (e.g. use-after-scope, or different thread's stack).
285+
StackTrace alloca_stack;
286+
alloca_stack.trace[0] = frame_pc + 16;
287+
alloca_stack.size = 1;
261288
Printf("%s", d.EndLocation());
289+
PrintStack(&alloca_stack);
262290
// Report the number of stack objects.
263291
char *p;
264-
uptr n_objects = internal_simple_strtoll(name_end, &p, 10);
292+
uptr n_objects = internal_simple_strtoll(frame_descr, &p, 10);
265293
CHECK(n_objects > 0);
266294
Printf(" This frame has %zu object(s):\n", n_objects);
267295
// Report all objects in this frame.
@@ -313,26 +341,6 @@ static void DescribeAccessToHeapChunk(AsanChunkView chunk, uptr addr,
313341
Printf("%s", d.EndLocation());
314342
}
315343

316-
// Return " (thread_name) " or an empty string if the name is empty.
317-
const char *ThreadNameWithParenthesis(AsanThreadContext *t, char buff[],
318-
uptr buff_len) {
319-
const char *name = t->name;
320-
if (name[0] == '\0') return "";
321-
buff[0] = 0;
322-
internal_strncat(buff, " (", 3);
323-
internal_strncat(buff, name, buff_len - 4);
324-
internal_strncat(buff, ")", 2);
325-
return buff;
326-
}
327-
328-
const char *ThreadNameWithParenthesis(u32 tid, char buff[],
329-
uptr buff_len) {
330-
if (tid == kInvalidTid) return "";
331-
asanThreadRegistry().CheckLocked();
332-
AsanThreadContext *t = GetThreadContextByTidLocked(tid);
333-
return ThreadNameWithParenthesis(t, buff, buff_len);
334-
}
335-
336344
void DescribeHeapAddress(uptr addr, uptr access_size) {
337345
AsanChunkView chunk = FindHeapChunkByAddress(addr);
338346
if (!chunk.IsValid()) return;

lib/asan/asan_thread.cc

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,14 +151,16 @@ void AsanThread::ClearShadowForThreadStack() {
151151
PoisonShadow(stack_bottom_, stack_top_ - stack_bottom_, 0);
152152
}
153153

154-
const char *AsanThread::GetFrameNameByAddr(uptr addr, uptr *offset) {
154+
const char *AsanThread::GetFrameNameByAddr(uptr addr, uptr *offset,
155+
uptr *frame_pc) {
155156
uptr bottom = 0;
156157
if (AddrIsInStack(addr)) {
157158
bottom = stack_bottom();
158159
} else {
159160
bottom = fake_stack().AddrIsInFakeStack(addr);
160161
CHECK(bottom);
161162
*offset = addr - bottom;
163+
*frame_pc = ((uptr*)bottom)[2];
162164
return (const char *)((uptr*)bottom)[1];
163165
}
164166
uptr aligned_addr = addr & ~(SANITIZER_WORDSIZE/8 - 1); // align addr.
@@ -183,6 +185,7 @@ const char *AsanThread::GetFrameNameByAddr(uptr addr, uptr *offset) {
183185
uptr* ptr = (uptr*)SHADOW_TO_MEM((uptr)(shadow_ptr + 1));
184186
CHECK(ptr[0] == kCurrentStackFrameMagic);
185187
*offset = addr - (uptr)ptr;
188+
*frame_pc = ptr[2];
186189
return (const char*)ptr[1];
187190
}
188191

lib/asan/asan_thread.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ class AsanThread {
6666
AsanThreadContext *context() { return context_; }
6767
void set_context(AsanThreadContext *context) { context_ = context; }
6868

69-
const char *GetFrameNameByAddr(uptr addr, uptr *offset);
69+
const char *GetFrameNameByAddr(uptr addr, uptr *offset, uptr *frame_pc);
7070

7171
bool AddrIsInStack(uptr addr) {
7272
return addr >= stack_bottom_ && addr < stack_top_;

lib/asan/lit_tests/Linux/zero-base-shadow.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,8 @@ int main(int argc, char **argv) {
1818
int res = x[argc * 10]; // BOOOM
1919
// CHECK: {{READ of size 1 at 0x.* thread T0}}
2020
// CHECK: {{ #0 0x.* in _?main .*zero-base-shadow.cc:}}[[@LINE-2]]
21-
// CHECK: {{Address 0x.* is .* frame <main>}}
21+
// CHECK: {{Address 0x.* is .* frame}}
22+
// CHECK: main
2223

2324
// Check that shadow for stack memory occupies lower part of address space.
2425
// CHECK-64: =>0x0f{{.*}}

lib/asan/lit_tests/stack-frame-demangle.cc

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
// Check that ASan is able to print demangled frame name even w/o
2-
// symbolization.
3-
4-
// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | FileCheck %s
1+
// RUN: %clangxx_asan -m64 -O0 %s -o %t && %t 2>&1 | %symbolize | FileCheck %s
52

63
#include <string.h>
74

@@ -11,9 +8,10 @@ struct YYY {
118
char array[10];
129
memset(array, 0, 10);
1310
return array[x]; // BOOOM
14-
// CHECK: {{ERROR: AddressSanitizer: stack-buffer-overflow}}
15-
// CHECK: {{READ of size 1 at 0x.* thread T0}}
16-
// CHECK: {{Address 0x.* is .* frame <XXX::YYY::ZZZ(.*)>}}
11+
// CHECK: ERROR: AddressSanitizer: stack-buffer-overflow
12+
// CHECK: READ of size 1 at
13+
// CHECK: is located in stack of thread T0 at offset
14+
// CHECK: XXX::YYY::ZZZ
1715
}
1816
};
1917
} // namespace XXX
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// RUN: %clangxx_asan -m64 -O1 %s -o %t
2+
// RUN: %t 0 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK0
3+
// RUN: %t 1 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK1
4+
// RUN: %t 2 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK2
5+
// RUN: %t 3 2>&1 | %symbolize | FileCheck %s --check-prefix=CHECK3
6+
7+
#define NOINLINE __attribute__((noinline))
8+
inline void break_optimization(void *arg) {
9+
__asm__ __volatile__("" : : "r" (arg) : "memory");
10+
}
11+
12+
NOINLINE static void Frame0(int frame, char *a, char *b, char *c) {
13+
char s[4] = {0};
14+
char *d = s;
15+
break_optimization(&d);
16+
switch (frame) {
17+
case 3: a[5]++; break;
18+
case 2: b[5]++; break;
19+
case 1: c[5]++; break;
20+
case 0: d[5]++; break;
21+
}
22+
}
23+
NOINLINE static void Frame1(int frame, char *a, char *b) {
24+
char c[4] = {0}; Frame0(frame, a, b, c);
25+
break_optimization(0);
26+
}
27+
NOINLINE static void Frame2(int frame, char *a) {
28+
char b[4] = {0}; Frame1(frame, a, b);
29+
break_optimization(0);
30+
}
31+
NOINLINE static void Frame3(int frame) {
32+
char a[4] = {0}; Frame2(frame, a);
33+
break_optimization(0);
34+
}
35+
36+
int main(int argc, char **argv) {
37+
if (argc != 2) return 1;
38+
Frame3(argv[1][0] - '0');
39+
}
40+
41+
// CHECK0: AddressSanitizer: stack-buffer-overflow
42+
// CHECK0: #0{{.*}}Frame0
43+
// CHECK0: #1{{.*}}Frame1
44+
// CHECK0: #2{{.*}}Frame2
45+
// CHECK0: #3{{.*}}Frame3
46+
// CHECK0: is located in stack of thread T0 at offset
47+
// CHECK0-NEXT: #0{{.*}}Frame0
48+
//
49+
// CHECK1: AddressSanitizer: stack-buffer-overflow
50+
// CHECK1: is located in stack of thread T0 at offset
51+
// CHECK1-NEXT: #0{{.*}}Frame1
52+
//
53+
// CHECK2: AddressSanitizer: stack-buffer-overflow
54+
// CHECK2: is located in stack of thread T0 at offset
55+
// CHECK2-NEXT: #0{{.*}}Frame2
56+
//
57+
// CHECK3: AddressSanitizer: stack-buffer-overflow
58+
// CHECK3: is located in stack of thread T0 at offset
59+
// CHECK3-NEXT: #0{{.*}}Frame3

lib/asan/lit_tests/stack-overflow.cc

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ int main(int argc, char **argv) {
1414
int res = x[argc * 10]; // BOOOM
1515
// CHECK: {{READ of size 1 at 0x.* thread T0}}
1616
// CHECK: {{ #0 0x.* in _?main .*stack-overflow.cc:}}[[@LINE-2]]
17-
// CHECK: {{Address 0x.* is .* frame <main>}}
17+
// CHECK: {{Address 0x.* is located in stack of thread T0 at offset}}
18+
// CHECK: main
1819
return res;
1920
}

lib/asan/lit_tests/use-after-scope-inlined.cc

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,8 @@ int main(int argc, char *argv[]) {
2323
// CHECK: READ of size 4 at 0x{{.*}} thread T0
2424
// CHECK: #0 0x{{.*}} in {{_?}}main
2525
// CHECK: {{.*}}use-after-scope-inlined.cc:[[@LINE-4]]
26-
// CHECK: Address 0x{{.*}} is located at offset
27-
// CHECK: [[OFFSET:[^ ]*]] in frame <main> of T0{{.*}}:
26+
// CHECK: Address 0x{{.*}} is located in stack of thread T0 at offset
27+
// CHECK: [[OFFSET:[^ ]*]] in frame
28+
// CHECK: main
2829
// CHECK: {{\[}}[[OFFSET]], {{.*}}) 'x.i'
2930
}

lib/asan/tests/asan_test.cc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -465,6 +465,9 @@ TEST(AddressSanitizer, ManyStackObjectsTest) {
465465
EXPECT_DEATH(Ident(ZZZ)[-1] = 0, ASAN_PCRE_DOTALL "XXX.*YYY.*ZZZ");
466466
}
467467

468+
#if 0 // This test requires online symbolizer.
469+
// Moved to lit_tests/stack-oob-frames.cc.
470+
// Reenable here once we have online symbolizer by default.
468471
NOINLINE static void Frame0(int frame, char *a, char *b, char *c) {
469472
char d[4] = {0};
470473
char *D = Ident(d);
@@ -500,6 +503,7 @@ TEST(AddressSanitizer, GuiltyStackFrame2Test) {
500503
TEST(AddressSanitizer, GuiltyStackFrame3Test) {
501504
EXPECT_DEATH(Frame3(3), "located .*in frame <.*Frame3");
502505
}
506+
#endif
503507

504508
NOINLINE void LongJmpFunc1(jmp_buf buf) {
505509
// create three red zones for these two stack objects.

0 commit comments

Comments
 (0)