Skip to content

Commit aff6cb4

Browse files
authored
Reland "[scudo] resize stack depot for allocation ring buffer" (#81028)
First commit of the stack is a clean reland, second is the fix. There was a typo in the `static_assert` that meant we were asserting the size of the pointer, not the struct. Also changed `alignas` to be more intuitive, but that is NFC. Ran builds in Android here: https://r.android.com/2954411
1 parent ea9ec80 commit aff6cb4

File tree

6 files changed

+219
-69
lines changed

6 files changed

+219
-69
lines changed

compiler-rt/lib/scudo/standalone/combined.h

Lines changed: 97 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#ifndef SCUDO_COMBINED_H_
1010
#define SCUDO_COMBINED_H_
1111

12+
#include "atomic_helpers.h"
1213
#include "chunk.h"
1314
#include "common.h"
1415
#include "flags.h"
@@ -282,15 +283,15 @@ class Allocator {
282283
return reinterpret_cast<void *>(addHeaderTag(reinterpret_cast<uptr>(Ptr)));
283284
}
284285

285-
NOINLINE u32 collectStackTrace() {
286+
NOINLINE u32 collectStackTrace(UNUSED StackDepot *Depot) {
286287
#ifdef HAVE_ANDROID_UNSAFE_FRAME_POINTER_CHASE
287288
// Discard collectStackTrace() frame and allocator function frame.
288289
constexpr uptr DiscardFrames = 2;
289290
uptr Stack[MaxTraceSize + DiscardFrames];
290291
uptr Size =
291292
android_unsafe_frame_pointer_chase(Stack, MaxTraceSize + DiscardFrames);
292293
Size = Min<uptr>(Size, MaxTraceSize + DiscardFrames);
293-
return Depot.insert(Stack + Min<uptr>(DiscardFrames, Size), Stack + Size);
294+
return Depot->insert(Stack + Min<uptr>(DiscardFrames, Size), Stack + Size);
294295
#else
295296
return 0;
296297
#endif
@@ -687,12 +688,12 @@ class Allocator {
687688
Quarantine.disable();
688689
Primary.disable();
689690
Secondary.disable();
690-
Depot.disable();
691+
Depot->disable();
691692
}
692693

693694
void enable() NO_THREAD_SAFETY_ANALYSIS {
694695
initThreadMaybe();
695-
Depot.enable();
696+
Depot->enable();
696697
Secondary.enable();
697698
Primary.enable();
698699
Quarantine.enable();
@@ -915,8 +916,14 @@ class Allocator {
915916
Primary.Options.clear(OptionBit::AddLargeAllocationSlack);
916917
}
917918

918-
const char *getStackDepotAddress() const {
919-
return reinterpret_cast<const char *>(&Depot);
919+
const char *getStackDepotAddress() {
920+
initThreadMaybe();
921+
return reinterpret_cast<char *>(Depot);
922+
}
923+
924+
uptr getStackDepotSize() {
925+
initThreadMaybe();
926+
return StackDepotSize;
920927
}
921928

922929
const char *getRegionInfoArrayAddress() const {
@@ -945,21 +952,35 @@ class Allocator {
945952
if (!Depot->find(Hash, &RingPos, &Size))
946953
return;
947954
for (unsigned I = 0; I != Size && I != MaxTraceSize; ++I)
948-
Trace[I] = static_cast<uintptr_t>((*Depot)[RingPos + I]);
955+
Trace[I] = static_cast<uintptr_t>(Depot->at(RingPos + I));
949956
}
950957

951958
static void getErrorInfo(struct scudo_error_info *ErrorInfo,
952959
uintptr_t FaultAddr, const char *DepotPtr,
953-
const char *RegionInfoPtr, const char *RingBufferPtr,
954-
size_t RingBufferSize, const char *Memory,
955-
const char *MemoryTags, uintptr_t MemoryAddr,
956-
size_t MemorySize) {
960+
size_t DepotSize, const char *RegionInfoPtr,
961+
const char *RingBufferPtr, size_t RingBufferSize,
962+
const char *Memory, const char *MemoryTags,
963+
uintptr_t MemoryAddr, size_t MemorySize) {
964+
// N.B. we need to support corrupted data in any of the buffers here. We get
965+
// this information from an external process (the crashing process) that
966+
// should not be able to crash the crash dumper (crash_dump on Android).
967+
// See also the get_error_info_fuzzer.
957968
*ErrorInfo = {};
958969
if (!allocatorSupportsMemoryTagging<Config>() ||
959970
MemoryAddr + MemorySize < MemoryAddr)
960971
return;
961972

962-
auto *Depot = reinterpret_cast<const StackDepot *>(DepotPtr);
973+
const StackDepot *Depot = nullptr;
974+
if (DepotPtr) {
975+
// check for corrupted StackDepot. First we need to check whether we can
976+
// read the metadata, then whether the metadata matches the size.
977+
if (DepotSize < sizeof(*Depot))
978+
return;
979+
Depot = reinterpret_cast<const StackDepot *>(DepotPtr);
980+
if (!Depot->isValid(DepotSize))
981+
return;
982+
}
983+
963984
size_t NextErrorReport = 0;
964985

965986
// Check for OOB in the current block and the two surrounding blocks. Beyond
@@ -1025,7 +1046,9 @@ class Allocator {
10251046
uptr GuardedAllocSlotSize = 0;
10261047
#endif // GWP_ASAN_HOOKS
10271048

1028-
StackDepot Depot;
1049+
StackDepot *Depot = nullptr;
1050+
uptr StackDepotSize = 0;
1051+
MemMapT RawStackDepotMap;
10291052

10301053
struct AllocationRingBuffer {
10311054
struct Entry {
@@ -1234,11 +1257,18 @@ class Allocator {
12341257
storeEndMarker(RoundNewPtr, NewSize, BlockEnd);
12351258
}
12361259

1237-
void storePrimaryAllocationStackMaybe(const Options &Options, void *Ptr) {
1260+
StackDepot *getDepotIfEnabled(const Options &Options) {
12381261
if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))
1262+
return nullptr;
1263+
return Depot;
1264+
}
1265+
1266+
void storePrimaryAllocationStackMaybe(const Options &Options, void *Ptr) {
1267+
auto *Depot = getDepotIfEnabled(Options);
1268+
if (!Depot)
12391269
return;
12401270
auto *Ptr32 = reinterpret_cast<u32 *>(Ptr);
1241-
Ptr32[MemTagAllocationTraceIndex] = collectStackTrace();
1271+
Ptr32[MemTagAllocationTraceIndex] = collectStackTrace(Depot);
12421272
Ptr32[MemTagAllocationTidIndex] = getThreadID();
12431273
}
12441274

@@ -1268,10 +1298,10 @@ class Allocator {
12681298

12691299
void storeSecondaryAllocationStackMaybe(const Options &Options, void *Ptr,
12701300
uptr Size) {
1271-
if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))
1301+
auto *Depot = getDepotIfEnabled(Options);
1302+
if (!Depot)
12721303
return;
1273-
1274-
u32 Trace = collectStackTrace();
1304+
u32 Trace = collectStackTrace(Depot);
12751305
u32 Tid = getThreadID();
12761306

12771307
auto *Ptr32 = reinterpret_cast<u32 *>(Ptr);
@@ -1283,14 +1313,14 @@ class Allocator {
12831313

12841314
void storeDeallocationStackMaybe(const Options &Options, void *Ptr,
12851315
u8 PrevTag, uptr Size) {
1286-
if (!UNLIKELY(Options.get(OptionBit::TrackAllocationStacks)))
1316+
auto *Depot = getDepotIfEnabled(Options);
1317+
if (!Depot)
12871318
return;
1288-
12891319
auto *Ptr32 = reinterpret_cast<u32 *>(Ptr);
12901320
u32 AllocationTrace = Ptr32[MemTagAllocationTraceIndex];
12911321
u32 AllocationTid = Ptr32[MemTagAllocationTidIndex];
12921322

1293-
u32 DeallocationTrace = collectStackTrace();
1323+
u32 DeallocationTrace = collectStackTrace(Depot);
12941324
u32 DeallocationTid = getThreadID();
12951325

12961326
storeRingBufferEntry(addFixedTag(untagPointer(Ptr), PrevTag),
@@ -1369,8 +1399,10 @@ class Allocator {
13691399
UntaggedFaultAddr < ChunkAddr ? BUFFER_UNDERFLOW : BUFFER_OVERFLOW;
13701400
R->allocation_address = ChunkAddr;
13711401
R->allocation_size = Header.SizeOrUnusedBytes;
1372-
collectTraceMaybe(Depot, R->allocation_trace,
1373-
Data[MemTagAllocationTraceIndex]);
1402+
if (Depot) {
1403+
collectTraceMaybe(Depot, R->allocation_trace,
1404+
Data[MemTagAllocationTraceIndex]);
1405+
}
13741406
R->allocation_tid = Data[MemTagAllocationTidIndex];
13751407
return NextErrorReport == NumErrorReports;
13761408
};
@@ -1393,7 +1425,7 @@ class Allocator {
13931425
auto *RingBuffer =
13941426
reinterpret_cast<const AllocationRingBuffer *>(RingBufferPtr);
13951427
size_t RingBufferElements = ringBufferElementsFromBytes(RingBufferSize);
1396-
if (!RingBuffer || RingBufferElements == 0)
1428+
if (!RingBuffer || RingBufferElements == 0 || !Depot)
13971429
return;
13981430
uptr Pos = atomic_load_relaxed(&RingBuffer->Pos);
13991431

@@ -1483,6 +1515,43 @@ class Allocator {
14831515
return;
14841516
u32 AllocationRingBufferSize =
14851517
static_cast<u32>(getFlags()->allocation_ring_buffer_size);
1518+
1519+
// We store alloc and free stacks for each entry.
1520+
constexpr u32 kStacksPerRingBufferEntry = 2;
1521+
constexpr u32 kMaxU32Pow2 = ~(UINT32_MAX >> 1);
1522+
static_assert(isPowerOfTwo(kMaxU32Pow2));
1523+
constexpr u32 kFramesPerStack = 8;
1524+
static_assert(isPowerOfTwo(kFramesPerStack));
1525+
1526+
// We need StackDepot to be aligned to 8-bytes so the ring we store after
1527+
// is correctly assigned.
1528+
static_assert(sizeof(StackDepot) % alignof(atomic_u64) == 0);
1529+
1530+
// Make sure the maximum sized StackDepot fits withint a uintptr_t to
1531+
// simplify the overflow checking.
1532+
static_assert(sizeof(StackDepot) + UINT32_MAX * sizeof(atomic_u64) *
1533+
UINT32_MAX * sizeof(atomic_u32) <
1534+
UINTPTR_MAX);
1535+
1536+
if (AllocationRingBufferSize > kMaxU32Pow2 / kStacksPerRingBufferEntry)
1537+
return;
1538+
u32 TabSize = static_cast<u32>(roundUpPowerOfTwo(kStacksPerRingBufferEntry *
1539+
AllocationRingBufferSize));
1540+
if (TabSize > UINT32_MAX / kFramesPerStack)
1541+
return;
1542+
u32 RingSize = static_cast<u32>(TabSize * kFramesPerStack);
1543+
DCHECK(isPowerOfTwo(RingSize));
1544+
1545+
StackDepotSize = sizeof(StackDepot) + sizeof(atomic_u64) * RingSize +
1546+
sizeof(atomic_u32) * TabSize;
1547+
MemMapT DepotMap;
1548+
DepotMap.map(
1549+
/*Addr=*/0U, roundUp(StackDepotSize, getPageSizeCached()),
1550+
"scudo:stack_depot");
1551+
Depot = reinterpret_cast<StackDepot *>(DepotMap.getBase());
1552+
Depot->init(RingSize, TabSize);
1553+
RawStackDepotMap = DepotMap;
1554+
14861555
MemMapT MemMap;
14871556
MemMap.map(
14881557
/*Addr=*/0U,
@@ -1505,6 +1574,10 @@ class Allocator {
15051574
RawRingBufferMap.getCapacity());
15061575
}
15071576
RawRingBuffer = nullptr;
1577+
if (Depot) {
1578+
RawStackDepotMap.unmap(RawStackDepotMap.getBase(),
1579+
RawStackDepotMap.getCapacity());
1580+
}
15081581
}
15091582

15101583
static constexpr size_t ringBufferSizeInBytes(u32 RingBufferElements) {

compiler-rt/lib/scudo/standalone/fuzz/get_error_info_fuzzer.cpp

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
#define SCUDO_FUZZ
1010
#include "allocator_config.h"
1111
#include "combined.h"
12+
#include "common.h"
1213

1314
#include <fuzzer/FuzzedDataProvider.h>
1415

@@ -31,11 +32,6 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *Data, size_t Size) {
3132

3233
std::string StackDepotBytes =
3334
FDP.ConsumeRandomLengthString(FDP.remaining_bytes());
34-
std::vector<char> StackDepot(sizeof(scudo::StackDepot), 0);
35-
for (size_t i = 0; i < StackDepotBytes.length() && i < StackDepot.size();
36-
++i) {
37-
StackDepot[i] = StackDepotBytes[i];
38-
}
3935

4036
std::string RegionInfoBytes =
4137
FDP.ConsumeRandomLengthString(FDP.remaining_bytes());
@@ -48,9 +44,9 @@ extern "C" int LLVMFuzzerTestOneInput(uint8_t *Data, size_t Size) {
4844
std::string RingBufferBytes = FDP.ConsumeRemainingBytesAsString();
4945

5046
scudo_error_info ErrorInfo;
51-
AllocatorT::getErrorInfo(&ErrorInfo, FaultAddr, StackDepot.data(),
52-
RegionInfo.data(), RingBufferBytes.data(),
53-
RingBufferBytes.size(), Memory, MemoryTags,
54-
MemoryAddr, MemorySize);
47+
AllocatorT::getErrorInfo(&ErrorInfo, FaultAddr, StackDepotBytes.data(),
48+
StackDepotBytes.size(), RegionInfo.data(),
49+
RingBufferBytes.data(), RingBufferBytes.size(),
50+
Memory, MemoryTags, MemoryAddr, MemorySize);
5551
return 0;
5652
}

compiler-rt/lib/scudo/standalone/platform.h

Lines changed: 0 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -63,16 +63,6 @@
6363
#define SCUDO_CAN_USE_MTE (SCUDO_LINUX || SCUDO_TRUSTY)
6464
#endif
6565

66-
// Use smaller table sizes for fuzzing in order to reduce input size.
67-
// Trusty just has less available memory.
68-
#ifndef SCUDO_SMALL_STACK_DEPOT
69-
#if defined(SCUDO_FUZZ) || SCUDO_TRUSTY
70-
#define SCUDO_SMALL_STACK_DEPOT 1
71-
#else
72-
#define SCUDO_SMALL_STACK_DEPOT 0
73-
#endif
74-
#endif
75-
7666
#ifndef SCUDO_ENABLE_HOOKS
7767
#define SCUDO_ENABLE_HOOKS 0
7868
#endif

0 commit comments

Comments
 (0)