Skip to content

Commit ed47840

Browse files
committed
[msan] Mark allocator padding as uninitialized, with new origin tag
This is follow-up work per discussion in llvm#155944 (comment). If the allocator reserves more space than the user requested (e.g., malloc(7) actually has 16 bytes reserved), the padding bytes will now be marked as uninitialized; the origin will be set as a new tag, ALLOC_PADDING (in the case of ambiguity caused by origin granularity, ALLOC will take precedence). Padding poisoning is controlled by the existing flag poison_in_malloc and a new flag, poison_in_calloc.
1 parent b9b0ea5 commit ed47840

File tree

6 files changed

+97
-8
lines changed

6 files changed

+97
-8
lines changed

compiler-rt/lib/msan/msan.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -303,6 +303,7 @@ u32 ChainOrigin(u32 id, StackTrace *stack);
303303
const int STACK_TRACE_TAG_POISON = StackTrace::TAG_CUSTOM + 1;
304304
const int STACK_TRACE_TAG_FIELDS = STACK_TRACE_TAG_POISON + 1;
305305
const int STACK_TRACE_TAG_VPTR = STACK_TRACE_TAG_FIELDS + 1;
306+
const int STACK_TRACE_TAG_ALLOC_PADDING = STACK_TRACE_TAG_VPTR + 1;
306307

307308
#define GET_MALLOC_STACK_TRACE \
308309
UNINITIALIZED BufferedStackTrace stack; \

compiler-rt/lib/msan/msan_allocator.cpp

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -217,25 +217,42 @@ static void *MsanAllocate(BufferedStackTrace *stack, uptr size, uptr alignment,
217217
}
218218
auto *meta = reinterpret_cast<Metadata *>(allocator.GetMetaData(allocated));
219219
meta->requested_size = size;
220+
uptr actually_allocated_size = allocator.GetActuallyAllocatedSize(allocated);
221+
void* padding_start =
222+
reinterpret_cast<void*>(reinterpret_cast<uptr>(allocated) + size);
223+
uptr padding_size = actually_allocated_size - size;
224+
225+
// Origins have 4-byte granularity. Set the TAG_ALLOC_PADDING origin first,
226+
// so the TAG_ALLOC origin will take precedence if necessary e.g.,
227+
// - if we have malloc(7) that actually takes up 16 bytes, bytes 0-7 will
228+
// have TAG_ALLOC and bytes 8-15 will have TAG_ALLOC_PADDING.
229+
// - with calloc, bytes 4-15 will have TAG_ALLOC_PADDING (but bytes 4-6 are
230+
// initialized, hence the origin is unused). Note that this means, unlike
231+
// with malloc, byte 7 is TAG_ALLOC_PADDING instead of TAG_ALLOC.
232+
if (__msan_get_track_origins() && ((zero && flags()->poison_in_calloc) ||
233+
(!zero && flags()->poison_in_malloc))) {
234+
stack->tag = STACK_TRACE_TAG_ALLOC_PADDING;
235+
Origin o2 = Origin::CreateHeapOrigin(stack);
236+
__msan_set_origin(padding_start, padding_size, o2.raw_id());
237+
}
238+
220239
if (zero) {
221240
if (allocator.FromPrimary(allocated))
222241
__msan_clear_and_unpoison(allocated, size);
223242
else
224243
__msan_unpoison(allocated, size); // Mem is already zeroed.
244+
245+
if (flags()->poison_in_calloc)
246+
__msan_poison(padding_start, padding_size);
225247
} else if (flags()->poison_in_malloc) {
226-
__msan_poison(allocated, size);
248+
__msan_poison(allocated, actually_allocated_size);
227249
if (__msan_get_track_origins()) {
228250
stack->tag = StackTrace::TAG_ALLOC;
229251
Origin o = Origin::CreateHeapOrigin(stack);
230252
__msan_set_origin(allocated, size, o.raw_id());
231253
}
232254
}
233255

234-
uptr actually_allocated_size = allocator.GetActuallyAllocatedSize(allocated);
235-
// For compatibility, the allocator converted 0-sized allocations into 1 byte
236-
if (size == 0 && actually_allocated_size > 0 && flags()->poison_in_malloc)
237-
__msan_poison(allocated, 1);
238-
239256
UnpoisonParam(2);
240257
RunMallocHooks(allocated, size);
241258
return allocated;
@@ -247,7 +264,7 @@ void __msan::MsanDeallocate(BufferedStackTrace *stack, void *p) {
247264
RunFreeHooks(p);
248265

249266
Metadata *meta = reinterpret_cast<Metadata *>(allocator.GetMetaData(p));
250-
uptr size = meta->requested_size;
267+
uptr size = allocator.GetActuallyAllocatedSize(p);
251268
meta->requested_size = 0;
252269
// This memory will not be reused by anyone else, so we are free to keep it
253270
// poisoned. The secondary allocator will unmap and unpoison by

compiler-rt/lib/msan/msan_flags.inc

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ MSAN_FLAG(int, origin_history_size, Origin::kMaxDepth, "")
2222
MSAN_FLAG(int, origin_history_per_stack_limit, 20000, "")
2323
MSAN_FLAG(bool, poison_heap_with_zeroes, false, "")
2424
MSAN_FLAG(bool, poison_stack_with_zeroes, false, "")
25+
MSAN_FLAG(bool, poison_in_calloc, true, "")
2526
MSAN_FLAG(bool, poison_in_malloc, true, "")
2627
MSAN_FLAG(bool, poison_in_free, true, "")
2728
MSAN_FLAG(bool, poison_in_dtor, true, "")

compiler-rt/lib/msan/msan_report.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,11 @@ static void DescribeOrigin(u32 id) {
9090
Printf(" %sVirtual table ptr was destroyed%s\n", d.Origin(),
9191
d.Default());
9292
break;
93+
case STACK_TRACE_TAG_ALLOC_PADDING:
94+
Printf(
95+
" %sUninitialized value was created by heap allocator padding%s\n",
96+
d.Origin(), d.Default());
97+
break;
9398
default:
9499
Printf(" %sUninitialized value was created%s\n", d.Origin(),
95100
d.Default());
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
// malloc: all bytes are uninitialized
2+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 %s -o %t && not %run %t 0 2>&1 \
3+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC
4+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 %s -o %t && not %run %t 6 2>&1 \
5+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC
6+
//
7+
// This test assumes the allocator allocates 16 bytes for malloc(7). Bytes
8+
// 7-15 are padding.
9+
// Edge case: when the origin granularity spans both ALLOC and ALLOC_PADDING,
10+
// ALLOC takes precedence
11+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 %s -o %t && not %run %t 7 2>&1 \
12+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC
13+
//
14+
// Bytes 8-15 are tagged as ALLOC_PADDING.
15+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 %s -o %t && not %run %t 8 2>&1 \
16+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC-PADDING
17+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 %s -o %t && not %run %t 15 2>&1 \
18+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC-PADDING
19+
20+
// calloc
21+
// Bytes 0-6 are fully initialized, so no MSan report should happen.
22+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 -DUSE_CALLOC %s -o %t && %run %t 0 2>&1
23+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 -DUSE_CALLOC %s -o %t && %run %t 6 2>&1
24+
//
25+
// Byte 7 is uninitialized. Unlike malloc, this is tagged as ALLOC_PADDING
26+
// (since the origin does not need to track bytes 4-6).
27+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 -DUSE_CALLOC %s -o %t && not %run %t 7 2>&1 \
28+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC-PADDING
29+
//
30+
// As with malloc, Bytes 8-15 are tagged as ALLOC_PADDING.
31+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 -DUSE_CALLOC %s -o %t && not %run %t 8 2>&1 \
32+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC-PADDING
33+
// RUN: %clang_msan -fsanitize-memory-track-origins=2 -DUSE_CALLOC %s -o %t && not %run %t 15 2>&1 \
34+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGIN-ALLOC-PADDING
35+
36+
#include <assert.h>
37+
#include <stdio.h>
38+
#include <stdlib.h>
39+
40+
int main(int argc, char **argv) {
41+
#ifdef USE_CALLOC
42+
char *p = (char *)calloc(7, 1);
43+
#else
44+
char *p = (char *)malloc(7);
45+
#endif
46+
47+
if (argc == 2) {
48+
int index = atoi(argv[1]);
49+
50+
printf("p[%d] = %d\n", index, p[index]);
51+
// CHECK: WARNING: MemorySanitizer: use-of-uninitialized-value
52+
// CHECK: {{#0 0x.* in main .*allocator_padding.cpp:}}[[@LINE-2]]
53+
// ORIGIN-ALLOC: Uninitialized value was created by a heap allocation
54+
// ORIGIN-ALLOC-PADDING: Uninitialized value was created by heap allocator padding
55+
free(p);
56+
}
57+
58+
return 0;
59+
}

compiler-rt/test/msan/zero_alloc.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
// RUN: %clang_msan -Wno-alloc-size -fsanitize-recover=memory %s -o %t && not %run %t 2>&1 | FileCheck %s
1+
// RUN: %clang_msan -Wno-alloc-size -fsanitize-recover=memory %s -o %t && not %run %t 2>&1 \
2+
// RUN: | FileCheck %s --check-prefix=CHECK
3+
// RUN: %clang_msan -Wno-alloc-size -fsanitize-recover=memory -fsanitize-memory-track-origins=2 %s -o %t && not %run %t 2>&1 \
4+
// RUN: | FileCheck %s --check-prefixes=CHECK,ORIGINS
25

36
#include <stdio.h>
47
#include <stdlib.h>
@@ -10,6 +13,7 @@ int main(int argc, char **argv) {
1013
printf("Content of p1 is: %d\n", *p1);
1114
// CHECK: WARNING: MemorySanitizer: use-of-uninitialized-value
1215
// CHECK: {{#0 0x.* in main .*zero_alloc.cpp:}}[[@LINE-2]]
16+
// ORIGINS: Uninitialized value was created by heap allocator padding
1317
free(p1);
1418
}
1519

@@ -19,6 +23,7 @@ int main(int argc, char **argv) {
1923
printf("Content of p2 is: %d\n", *p2);
2024
// CHECK: WARNING: MemorySanitizer: use-of-uninitialized-value
2125
// CHECK: {{#0 0x.* in main .*zero_alloc.cpp:}}[[@LINE-2]]
26+
// ORIGINS: Uninitialized value was created by heap allocator padding
2227
free(p2);
2328
}
2429

@@ -28,6 +33,7 @@ int main(int argc, char **argv) {
2833
printf("Content of p2 is: %d\n", *p3);
2934
// CHECK: WARNING: MemorySanitizer: use-of-uninitialized-value
3035
// CHECK: {{#0 0x.* in main .*zero_alloc.cpp:}}[[@LINE-2]]
36+
// ORIGINS: Uninitialized value was created by heap allocator padding
3137
free(p3);
3238
}
3339

0 commit comments

Comments
 (0)