Skip to content

Commit 9123284

Browse files
committed
[asan] Add experimental 'track_poison' flag
This adds an experimental flag that will keep track of where the manual memory poisoning (__asan_poison_memory_region) is called from, and print the stack trace if the poisoned region is accessed. (Currently, ASan will tell you what code accessed a poisoned region, but not which code set the poison.) This implementation performs best-effort record keeping using ring buffers, as suggested by Vitaly. The size of each ring buffer is set by the track_poison flag value. Some records may be lost in multi-threaded programs.
1 parent 80d5185 commit 9123284

File tree

7 files changed

+242
-5
lines changed

7 files changed

+242
-5
lines changed

compiler-rt/lib/asan/asan_descriptions.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
#define ASAN_DESCRIPTIONS_H
1616

1717
#include "asan_allocator.h"
18+
#include "asan_poisoning.h"
1819
#include "asan_thread.h"
1920
#include "sanitizer_common/sanitizer_common.h"
2021
#include "sanitizer_common/sanitizer_report_decorator.h"
@@ -46,6 +47,9 @@ class Decorator : public __sanitizer::SanitizerCommonDecorator {
4647
const char *Allocation() { return Magenta(); }
4748

4849
const char *ShadowByte(u8 byte) {
50+
if (IsPoisonTrackingMagic(byte))
51+
return Blue();
52+
4953
switch (byte) {
5054
case kAsanHeapLeftRedzoneMagic:
5155
case kAsanArrayCookieMagic:

compiler-rt/lib/asan/asan_errors.cpp

Lines changed: 64 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@
1212
//===----------------------------------------------------------------------===//
1313

1414
#include "asan_errors.h"
15+
1516
#include "asan_descriptions.h"
1617
#include "asan_mapping.h"
18+
#include "asan_poisoning.h"
1719
#include "asan_report.h"
1820
#include "asan_stack.h"
1921
#include "sanitizer_common/sanitizer_stackdepot.h"
@@ -505,6 +507,19 @@ ErrorGeneric::ErrorGeneric(u32 tid, uptr pc_, uptr bp_, uptr sp_, uptr addr,
505507
far_from_bounds = AdjacentShadowValuesAreFullyPoisoned(shadow_addr);
506508
break;
507509
}
510+
511+
if (flags()->track_poison > 0 && IsPoisonTrackingMagic(shadow_val)) {
512+
if (internal_strcmp(bug_descr, "unknown-crash") != 0) {
513+
Printf(
514+
"ERROR: use-after-poison tracking magic values overlap with "
515+
"other constants.\n");
516+
Printf("Please file a bug.\n");
517+
} else {
518+
bug_descr = "use-after-poison";
519+
bug_type_score = 20;
520+
}
521+
}
522+
508523
scariness.Scare(bug_type_score + read_after_free_bonus, bug_descr);
509524
if (far_from_bounds) scariness.Scare(10, "far-from-bounds");
510525
}
@@ -550,8 +565,12 @@ static void PrintLegend(InternalScopedString *str) {
550565
PrintShadowByte(str, " Global redzone: ", kAsanGlobalRedzoneMagic);
551566
PrintShadowByte(str, " Global init order: ",
552567
kAsanInitializationOrderMagic);
553-
PrintShadowByte(str, " Poisoned by user: ",
554-
kAsanUserPoisonedMemoryMagic);
568+
// TODO: sync description with PoisonTrackingMagicValues
569+
PrintShadowByte(
570+
str, " Poisoned by user: ", kAsanUserPoisonedMemoryMagic,
571+
flags()->track_poison > 0 ? " with detailed tracking using {0x80-0x8F, "
572+
"0x90-0x9F, 0xD0-0xDF, 0xE0-0xEF}\n"
573+
: "\n");
555574
PrintShadowByte(str, " Container overflow: ",
556575
kAsanContiguousContainerOOBMagic);
557576
PrintShadowByte(str, " Array cookie: ",
@@ -600,6 +619,44 @@ static void PrintShadowMemoryForAddress(uptr addr) {
600619
Printf("%s", str.data());
601620
}
602621

622+
static void CheckPoisonRecords(uptr addr) {
623+
if (!AddrIsInMem(addr))
624+
return;
625+
uptr shadow_addr = MemToShadow(addr);
626+
unsigned char poison_magic = *(reinterpret_cast<u8 *>(shadow_addr));
627+
int poison_index = PoisonTrackingMagicToIndex[poison_magic];
628+
629+
if (poison_index < 0 || poison_index >= NumPoisonTrackingMagicValues)
630+
return;
631+
632+
PoisonRecordRingBuffer *PoisonRecord =
633+
reinterpret_cast<PoisonRecordRingBuffer *>(PoisonRecords[poison_index]);
634+
if (PoisonRecord) {
635+
bool FoundMatch = false;
636+
637+
for (unsigned int i = 0; i < PoisonRecord->size(); i++) {
638+
struct PoisonRecord Record = (*PoisonRecord)[i];
639+
if (Record.begin <= addr && addr <= Record.end) {
640+
FoundMatch = true;
641+
642+
StackTrace poison_stack = StackDepotGet(Record.stack_id);
643+
644+
Printf("\n");
645+
Printf("Memory was manually poisoned by thread T%u:\n",
646+
Record.thread_id);
647+
poison_stack.Print();
648+
649+
break;
650+
}
651+
}
652+
653+
if (!FoundMatch) {
654+
Printf("ERROR: no matching poison tracking record found.\n");
655+
Printf("Try setting a larger track_poison value.\n");
656+
}
657+
}
658+
}
659+
603660
void ErrorGeneric::Print() {
604661
Decorator d;
605662
Printf("%s", d.Error());
@@ -623,6 +680,11 @@ void ErrorGeneric::Print() {
623680
PrintContainerOverflowHint();
624681
ReportErrorSummary(bug_descr, &stack);
625682
PrintShadowMemoryForAddress(addr);
683+
684+
// This uses a range of shadow values, hence it is not convenient to make a
685+
// specific error handler.
686+
if (flags()->track_poison > 0)
687+
CheckPoisonRecords(addr);
626688
}
627689

628690
} // namespace __asan

compiler-rt/lib/asan/asan_flags.inc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,9 @@ ASAN_FLAG(bool, poison_partial, true,
116116
"stack buffers.")
117117
ASAN_FLAG(bool, poison_array_cookie, true,
118118
"Poison (or not) the array cookie after operator new[].")
119+
ASAN_FLAG(int, track_poison, 0,
120+
"[EXPERIMENTAL] If non-zero, record the stack trace of manual "
121+
"memory poisoning calls.")
119122

120123
// Turn off alloc/dealloc mismatch checker on Mac and Windows for now.
121124
// https://github.com/google/sanitizers/issues/131

compiler-rt/lib/asan/asan_poisoning.cpp

Lines changed: 71 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,48 @@
2020
#include "sanitizer_common/sanitizer_flags.h"
2121
#include "sanitizer_common/sanitizer_interface_internal.h"
2222
#include "sanitizer_common/sanitizer_libc.h"
23+
#include "sanitizer_common/sanitizer_stackdepot.h"
2324

2425
namespace __asan {
2526

2627
static atomic_uint8_t can_poison_memory;
2728

29+
PoisonRecordRingBuffer *PoisonRecords[NumPoisonTrackingMagicValues] = {0};
30+
int PoisonTrackingMagicToIndex[256] = {-1};
31+
32+
void InitializePoisonTracking() {
33+
if (flags()->track_poison <= 0)
34+
return;
35+
36+
for (unsigned int i = 0; i < sizeof(PoisonTrackingMagicToIndex) / sizeof(int);
37+
i++) {
38+
PoisonTrackingMagicToIndex[i] = -1;
39+
}
40+
41+
for (unsigned int i = 0; i < NumPoisonTrackingMagicValues; i++) {
42+
int magic = PoisonTrackingIndexToMagic[i];
43+
CHECK(magic > 0);
44+
CHECK((unsigned int)magic <
45+
sizeof(PoisonTrackingMagicToIndex) / sizeof(int));
46+
47+
// Necessary for AddressIsPoisoned calculations
48+
CHECK((char)magic < 0);
49+
50+
PoisonTrackingMagicToIndex[magic] = i;
51+
52+
PoisonRecords[i] = PoisonRecordRingBuffer::New(flags()->track_poison);
53+
}
54+
}
55+
56+
bool IsPoisonTrackingMagic(int byte) {
57+
return (byte >= 0 &&
58+
(unsigned long)byte <
59+
(sizeof(PoisonTrackingMagicToIndex) / sizeof(int)) &&
60+
PoisonTrackingMagicToIndex[byte] >= 0 &&
61+
PoisonTrackingMagicToIndex[byte] < NumPoisonTrackingMagicValues &&
62+
PoisonTrackingIndexToMagic[PoisonTrackingMagicToIndex[byte]] == byte);
63+
}
64+
2865
void SetCanPoisonMemory(bool value) {
2966
atomic_store(&can_poison_memory, value, memory_order_release);
3067
}
@@ -107,6 +144,31 @@ void __asan_poison_memory_region(void const volatile *addr, uptr size) {
107144
uptr end_addr = beg_addr + size;
108145
VPrintf(3, "Trying to poison memory region [%p, %p)\n", (void *)beg_addr,
109146
(void *)end_addr);
147+
148+
u32 poison_magic = kAsanUserPoisonedMemoryMagic;
149+
150+
GET_CALLER_PC_BP;
151+
GET_STORE_STACK_TRACE_PC_BP(pc, bp);
152+
// TODO: garbage collect stacks once they fall off the ring buffer?
153+
// StackDepot doesn't currently have a way to delete stacks.
154+
u32 stack_id = StackDepotPut(stack);
155+
156+
if (flags()->track_poison > 0) {
157+
u32 current_tid = GetCurrentTidOrInvalid();
158+
u32 poison_index = ((stack_id * 151157) ^ (current_tid * 733123)) %
159+
NumPoisonTrackingMagicValues;
160+
poison_magic = PoisonTrackingIndexToMagic[poison_index];
161+
PoisonRecord record{.stack_id = stack_id,
162+
.thread_id = current_tid,
163+
.begin = beg_addr,
164+
.end = end_addr};
165+
// This is racy: with concurrent writes, some records may be lost,
166+
// but it's a sacrifice I am willing to make for speed.
167+
// The sharding across PoisonRecords reduces the likelihood of
168+
// concurrent writes.
169+
PoisonRecords[poison_index]->push(record);
170+
}
171+
110172
ShadowSegmentEndpoint beg(beg_addr);
111173
ShadowSegmentEndpoint end(end_addr);
112174
if (beg.chunk == end.chunk) {
@@ -119,7 +181,7 @@ void __asan_poison_memory_region(void const volatile *addr, uptr size) {
119181
if (beg.offset > 0) {
120182
*beg.chunk = Min(value, beg.offset);
121183
} else {
122-
*beg.chunk = kAsanUserPoisonedMemoryMagic;
184+
*beg.chunk = poison_magic;
123185
}
124186
}
125187
return;
@@ -134,10 +196,11 @@ void __asan_poison_memory_region(void const volatile *addr, uptr size) {
134196
}
135197
beg.chunk++;
136198
}
137-
REAL(memset)(beg.chunk, kAsanUserPoisonedMemoryMagic, end.chunk - beg.chunk);
199+
200+
REAL(memset)(beg.chunk, poison_magic, end.chunk - beg.chunk);
138201
// Poison if byte in end.offset is unaddressable.
139202
if (end.value > 0 && end.value <= end.offset) {
140-
*end.chunk = kAsanUserPoisonedMemoryMagic;
203+
*end.chunk = poison_magic;
141204
}
142205
}
143206

@@ -147,6 +210,11 @@ void __asan_unpoison_memory_region(void const volatile *addr, uptr size) {
147210
uptr end_addr = beg_addr + size;
148211
VPrintf(3, "Trying to unpoison memory region [%p, %p)\n", (void *)beg_addr,
149212
(void *)end_addr);
213+
214+
// Note: we don't need to update the poison tracking here. Since the shadow
215+
// memory will be unpoisoned, the poison tracking ring buffer entries will be
216+
// ignored.
217+
150218
ShadowSegmentEndpoint beg(beg_addr);
151219
ShadowSegmentEndpoint end(end_addr);
152220
if (beg.chunk == end.chunk) {

compiler-rt/lib/asan/asan_poisoning.h

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,15 +10,64 @@
1010
//
1111
// Shadow memory poisoning by ASan RTL and by user application.
1212
//===----------------------------------------------------------------------===//
13+
#ifndef ASAN_POISONING_H
14+
#define ASAN_POISONING_H
1315

1416
#include "asan_interceptors.h"
1517
#include "asan_internal.h"
1618
#include "asan_mapping.h"
1719
#include "sanitizer_common/sanitizer_flags.h"
1820
#include "sanitizer_common/sanitizer_platform.h"
21+
#include "sanitizer_common/sanitizer_ring_buffer.h"
22+
23+
// For platforms which support slow unwinder only, we restrict the store context
24+
// size to 1, basically only storing the current pc. We do this because the slow
25+
// unwinder which is based on libunwind is not async signal safe and causes
26+
// random freezes in forking applications as well as in signal handlers.
27+
#define GET_STORE_STACK_TRACE_PC_BP(pc, bp) \
28+
UNINITIALIZED BufferedStackTrace stack; \
29+
int max_stack = 16; \
30+
if (!SANITIZER_CAN_FAST_UNWIND) \
31+
max_stack = Min(max_stack, 1); \
32+
stack.Unwind(pc, bp, nullptr, common_flags()->fast_unwind_on_malloc, \
33+
max_stack);
34+
35+
#define GET_STORE_STACK_TRACE \
36+
GET_STORE_STACK_TRACE_PC_BP(StackTrace::GetCurrentPc(), GET_CURRENT_FRAME())
1937

2038
namespace __asan {
2139

40+
// These need to be negative chars (i.e., in the range [0x80 .. 0xff]) for
41+
// AddressIsPoisoned calculations.
42+
static const int PoisonTrackingIndexToMagic[] = {
43+
0x80, 0x81, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8a,
44+
0x8b, 0x8c, 0x8d, 0x8e, 0x8f, 0x90, 0x91, 0x92, 0x93, 0x94, 0x95,
45+
0x96, 0x97, 0x98, 0x99, 0x9a, 0x9b, 0x9c, 0x9d, 0x9e, 0x9f, 0xd0,
46+
0xd1, 0xd2, 0xd3, 0xd4, 0xd5, 0xd6, 0xd7, 0xd8, 0xd9, 0xda, 0xdb,
47+
0xdc, 0xdd, 0xde, 0xdf, 0xe0, 0xe1, 0xe2, 0xe3, 0xe4, 0xe5, 0xe6,
48+
0xe7, 0xe8, 0xe9, 0xea, 0xeb, 0xec, 0xed, 0xee, 0xef,
49+
};
50+
static const int NumPoisonTrackingMagicValues =
51+
sizeof(PoisonTrackingIndexToMagic) / sizeof(int);
52+
53+
extern int PoisonTrackingMagicToIndex[256];
54+
55+
struct PoisonRecord {
56+
unsigned int stack_id;
57+
unsigned int thread_id;
58+
uptr begin;
59+
uptr end;
60+
};
61+
62+
typedef RingBuffer<struct PoisonRecord> PoisonRecordRingBuffer;
63+
extern PoisonRecordRingBuffer* PoisonRecords[NumPoisonTrackingMagicValues];
64+
65+
// Set up data structures for track_poison.
66+
void InitializePoisonTracking();
67+
68+
// Is this number a magic value used for poison tracking?
69+
bool IsPoisonTrackingMagic(int byte);
70+
2271
// Enable/disable memory poisoning.
2372
void SetCanPoisonMemory(bool value);
2473
bool CanPoisonMemory();
@@ -96,3 +145,5 @@ ALWAYS_INLINE void FastPoisonShadowPartialRightRedzone(
96145
void FlushUnneededASanShadowMemory(uptr p, uptr size);
97146

98147
} // namespace __asan
148+
149+
#endif // ASAN_POISONING_H

compiler-rt/lib/asan/asan_rtl.cpp

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -460,6 +460,8 @@ static bool AsanInitInternal() {
460460
allocator_options.SetFrom(flags(), common_flags());
461461
InitializeAllocator(allocator_options);
462462

463+
InitializePoisonTracking();
464+
463465
if (SANITIZER_START_BACKGROUND_THREAD_IN_ASAN_INTERNAL)
464466
MaybeStartBackgroudThread();
465467

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
// Check that __asan_poison_memory_region and ASAN_OPTIONS=track_poison work.
2+
//
3+
// RUN: %clangxx_asan -O0 %s -o %t && env ASAN_OPTIONS=track_poison=1000 not %run %t 2>&1 | FileCheck %s --check-prefixes=CHECK-AC,CHECK-A
4+
// RUN: %clangxx_asan -O0 %s -o %t && env ASAN_OPTIONS=track_poison=1000 %run %t 20 2>&1 | FileCheck %s --check-prefixes=CHECK-B
5+
// RUN: %clangxx_asan -O0 %s -o %t && env ASAN_OPTIONS=track_poison=1000 not %run %t 30 30 2>&1 | FileCheck %s --check-prefixes=CHECK-AC,CHECK-C
6+
7+
#include <stdio.h>
8+
#include <stdlib.h>
9+
10+
extern "C" void __asan_poison_memory_region(void *, size_t);
11+
extern "C" void __asan_unpoison_memory_region(void *, size_t);
12+
13+
void novichok(char *x) {
14+
__asan_poison_memory_region(x, 64); // A
15+
__asan_unpoison_memory_region(x + 16, 8); // B
16+
__asan_poison_memory_region(x + 24, 16); // C
17+
}
18+
19+
void fsb(char *x) { novichok(x); }
20+
21+
int main(int argc, char **argv) {
22+
char *x = new char[64];
23+
x[10] = 0;
24+
fsb(x);
25+
// Bytes [ 0, 15]: poisoned by A
26+
// Bytes [16, 23]: unpoisoned by B
27+
// Bytes [24, 63]: poisoned by C
28+
29+
int res = x[argc * 10]; // BOOOM
30+
// CHECK-AC: ERROR: AddressSanitizer: use-after-poison
31+
// CHECK-AC: main{{.*}}use-after-poison-tracked.cpp:[[@LINE-2]]
32+
// CHECK-B-NOT: ERROR: AddressSanitizer: use-after-poison
33+
34+
// CHECK-AC: Memory was manually poisoned by thread T0:
35+
// CHECK-A: novichok{{.*}}use-after-poison-tracked.cpp:[[@LINE-21]]
36+
// CHECK-C: novichok{{.*}}use-after-poison-tracked.cpp:[[@LINE-20]]
37+
// CHECK-AC: fsb{{.*}}use-after-poison-tracked.cpp:[[@LINE-18]]
38+
// CHECK-AC: main{{.*}}use-after-poison-tracked.cpp:[[@LINE-14]]
39+
// CHECK-B-NOT: Memory was manually poisoned by thread T0:
40+
41+
delete[] x;
42+
43+
printf("End of program reached\n");
44+
// CHECK-B: End of program reached
45+
46+
return 0;
47+
}

0 commit comments

Comments
 (0)