Skip to content

Commit e274d23

Browse files
committed
scudo: Support free_sized and free_aligned_sized from C23
Signed-off-by: Justin King <jcking@google.com>
1 parent 569ca0f commit e274d23

File tree

16 files changed

+411
-82
lines changed

16 files changed

+411
-82
lines changed

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

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,10 +53,11 @@ namespace Chunk {
5353
// but https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414 prevents it from
5454
// happening, as it will error, complaining the number of bits is not enough.
5555
enum Origin : u8 {
56-
Malloc = 0,
57-
New = 1,
58-
NewArray = 2,
59-
Memalign = 3,
56+
Malloc = 0, // malloc, calloc, realloc, free, free_sized
57+
New = 1, // operator new, operator delete
58+
NewArray = 2, // operator new [], operator delete []
59+
AlignedAlloc = 3, // aligned_alloc, posix_memalign, memalign, pvalloc, valloc,
60+
// free_aligned_sized
6061
};
6162

6263
enum State : u8 { Available = 0, Allocated = 1, Quarantined = 2 };

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

Lines changed: 69 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -170,6 +170,12 @@ class Allocator {
170170
Primary.Options.set(OptionBit::DeallocTypeMismatch);
171171
if (getFlags()->delete_size_mismatch)
172172
Primary.Options.set(OptionBit::DeleteSizeMismatch);
173+
if (getFlags()->free_size_mismatch)
174+
Primary.Options.set(OptionBit::FreeSizeMismatch);
175+
if (getFlags()->free_alignment_mismatch)
176+
Primary.Options.set(OptionBit::FreeAlignmentMismatch);
177+
if (getFlags()->delete_alignment_mismatch)
178+
Primary.Options.set(OptionBit::DeleteAlignmentMismatch);
173179
if (allocatorSupportsMemoryTagging<AllocatorConfig>() &&
174180
systemSupportsMemoryTagging())
175181
Primary.Options.set(OptionBit::UseMemoryTagging);
@@ -433,7 +439,8 @@ class Allocator {
433439
}
434440

435441
NOINLINE void deallocate(void *Ptr, Chunk::Origin Origin, uptr DeleteSize = 0,
436-
UNUSED uptr Alignment = MinAlignment) {
442+
bool HasDeleteSize = false, uptr DeleteAlignment = 0,
443+
bool HasDeleteAlignment = false) {
437444
if (UNLIKELY(!Ptr))
438445
return;
439446

@@ -456,6 +463,9 @@ class Allocator {
456463
}
457464
#endif // GWP_ASAN_HOOKS
458465

466+
if (UNLIKELY(HasDeleteAlignment && !isPowerOfTwo(DeleteAlignment)))
467+
reportAlignmentNotPowerOfTwo(DeleteAlignment);
468+
459469
if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), MinAlignment)))
460470
reportMisalignedPointer(AllocatorAction::Deallocating, Ptr);
461471

@@ -470,19 +480,41 @@ class Allocator {
470480

471481
const Options Options = Primary.Options.load();
472482
if (Options.get(OptionBit::DeallocTypeMismatch)) {
473-
if (UNLIKELY(Header.OriginOrWasZeroed != Origin)) {
474-
// With the exception of memalign'd chunks, that can be still be free'd.
475-
if (Header.OriginOrWasZeroed != Chunk::Origin::Memalign ||
476-
Origin != Chunk::Origin::Malloc)
477-
reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,
478-
Header.OriginOrWasZeroed, Origin);
479-
}
483+
if (UNLIKELY(isOriginMismatch(
484+
static_cast<Chunk::Origin>(Header.OriginOrWasZeroed), Origin,
485+
HasDeleteSize)))
486+
reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,
487+
Header.OriginOrWasZeroed, Origin,
488+
HasDeleteSize);
480489
}
481490

482491
const uptr Size = getSize(Ptr, &Header);
483-
if (DeleteSize && Options.get(OptionBit::DeleteSizeMismatch)) {
484-
if (UNLIKELY(DeleteSize != Size))
485-
reportDeleteSizeMismatch(Ptr, DeleteSize, Size);
492+
switch (Origin) {
493+
case Chunk::Origin::New:
494+
FALLTHROUGH;
495+
case Chunk::Origin::NewArray:
496+
if (Options.get(OptionBit::DeleteSizeMismatch) && HasDeleteSize) {
497+
if (UNLIKELY(DeleteSize != Size))
498+
reportDeleteSizeMismatch(Ptr, DeleteSize, Size);
499+
}
500+
if (Options.get(OptionBit::DeleteAlignmentMismatch) &&
501+
HasDeleteAlignment) {
502+
if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), DeleteAlignment)))
503+
reportDeleteAlignmentMismatch(Ptr, DeleteAlignment);
504+
}
505+
break;
506+
case Chunk::Origin::AlignedAlloc:
507+
if (Options.get(OptionBit::FreeAlignmentMismatch) && HasDeleteAlignment) {
508+
if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), DeleteAlignment)))
509+
reportFreeAlignmentMismatch(Ptr, DeleteAlignment);
510+
}
511+
FALLTHROUGH;
512+
case Chunk::Origin::Malloc:
513+
if (Options.get(OptionBit::FreeSizeMismatch) && HasDeleteSize) {
514+
if (UNLIKELY(DeleteSize != Size))
515+
reportFreeSizeMismatch(Ptr, DeleteSize, Size);
516+
}
517+
break;
486518
}
487519

488520
quarantineOrDeallocateChunk(Options, TaggedPtr, &Header, Size);
@@ -529,14 +561,20 @@ class Allocator {
529561
if (UNLIKELY(Header.State != Chunk::State::Allocated))
530562
reportInvalidChunkState(AllocatorAction::Reallocating, OldPtr);
531563

532-
// Pointer has to be allocated with a malloc-type function. Some
533-
// applications think that it is OK to realloc a memalign'ed pointer, which
534-
// will trigger this check. It really isn't.
535564
if (Options.get(OptionBit::DeallocTypeMismatch)) {
536-
if (UNLIKELY(Header.OriginOrWasZeroed != Chunk::Origin::Malloc))
537-
reportDeallocTypeMismatch(AllocatorAction::Reallocating, OldPtr,
538-
Header.OriginOrWasZeroed,
539-
Chunk::Origin::Malloc);
565+
// There is no language prohibiting the use of realloc with
566+
// aligned_alloc/posix_memalign/memalign and etc. The outcome in
567+
// practice is that the newly allocated memory will typically not
568+
// have the same alignment but will have minimum alignment. With
569+
// regards to operator new, there is no guarantee that the allocator
570+
// being used with malloc is the same as operator new. There is also
571+
// no guarantee that they share the same minimum alignment guarantees.
572+
// So we reject these.
573+
if (UNLIKELY(Header.OriginOrWasZeroed == Chunk::Origin::New ||
574+
Header.OriginOrWasZeroed == Chunk::Origin::NewArray))
575+
reportDeallocTypeMismatch(
576+
AllocatorAction::Reallocating, OldPtr, Header.OriginOrWasZeroed,
577+
Chunk::Origin::Malloc, /*HasDeleteSize=*/false);
540578
}
541579

542580
void *BlockBegin = getBlockBegin(OldTaggedPtr, &Header);
@@ -1746,6 +1784,19 @@ class Allocator {
17461784
return (Bytes - sizeof(AllocationRingBuffer)) /
17471785
sizeof(typename AllocationRingBuffer::Entry);
17481786
}
1787+
1788+
static bool isOriginMismatch(Chunk::Origin Alloc, Chunk::Origin Dealloc,
1789+
bool HasDeleteSize) {
1790+
if (Alloc == Dealloc) {
1791+
return false;
1792+
}
1793+
if (Alloc == Chunk::Origin::AlignedAlloc &&
1794+
Dealloc == Chunk::Origin::Malloc && !HasDeleteSize) {
1795+
// aligned_alloc with free is allowed, but not free_sized.
1796+
return false;
1797+
}
1798+
return true;
1799+
}
17491800
};
17501801

17511802
} // namespace scudo

compiler-rt/lib/scudo/standalone/flags.inc

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,3 +49,18 @@ SCUDO_FLAG(int, release_to_os_interval_ms, 5000,
4949
SCUDO_FLAG(int, allocation_ring_buffer_size, 32768,
5050
"Entries to keep in the allocation ring buffer for scudo. "
5151
"Values less or equal to zero disable the buffer.")
52+
53+
SCUDO_FLAG(bool, delete_alignment_mismatch, true,
54+
"Terminate on an alignment mismatch between a aligned-delete and "
55+
"the actual "
56+
"alignment of a chunk (as provided to new/new[]).")
57+
58+
SCUDO_FLAG(bool, free_size_mismatch, true,
59+
"Terminate on a size mismatch between a free_sized and the actual "
60+
"size of a chunk (as provided to malloc/calloc/realloc).")
61+
62+
SCUDO_FLAG(bool, free_alignment_mismatch, true,
63+
"Terminate on an alignment mismatch between a free_aligned_sized "
64+
"and the actual "
65+
"alignment of a chunk (as provided to "
66+
"aligned_alloc/posix_memalign/memalign).")

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

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,15 @@
4747
#define UNUSED __attribute__((unused))
4848
#define USED __attribute__((used))
4949
#define NOEXCEPT noexcept
50+
#ifdef __has_attribute
51+
#if __has_attribute(fallthrough)
52+
#define FALLTHROUGH __attribute__((fallthrough))
53+
#else
54+
#define FALLTHROUGH
55+
#endif
56+
#else
57+
#define FALLTHROUGH
58+
#endif
5059

5160
// This check is only available on Clang. This is essentially an alias of
5261
// C++20's 'constinit' specifier which will take care of this when (if?) we can

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ enum class OptionBit {
2525
UseOddEvenTags,
2626
UseMemoryTagging,
2727
AddLargeAllocationSlack,
28+
DeleteAlignmentMismatch,
29+
FreeSizeMismatch,
30+
FreeAlignmentMismatch,
2831
};
2932

3033
struct Options {

compiler-rt/lib/scudo/standalone/report.cpp

Lines changed: 60 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -142,13 +142,48 @@ void NORETURN reportMisalignedPointer(AllocatorAction Action, const void *Ptr) {
142142
stringifyAction(Action), Ptr);
143143
}
144144

145+
static const char *stringifyAllocOrigin(Chunk::Origin AllocOrigin) {
146+
switch (AllocOrigin) {
147+
case Chunk::Origin::Malloc:
148+
return "malloc";
149+
case Chunk::Origin::New:
150+
return "operator new";
151+
case Chunk::Origin::NewArray:
152+
return "operator new []";
153+
case Chunk::Origin::AlignedAlloc:
154+
return "aligned_alloc";
155+
}
156+
return "<invalid origin>";
157+
}
158+
159+
static const char *stringifyDeallocOrigin(Chunk::Origin DeallocOrigin,
160+
bool HasDeleteSize) {
161+
switch (DeallocOrigin) {
162+
case Chunk::Origin::Malloc:
163+
if (HasDeleteSize)
164+
return "free_sized";
165+
return "free";
166+
case Chunk::Origin::New:
167+
return "operator delete";
168+
case Chunk::Origin::NewArray:
169+
return "operator delete []";
170+
case Chunk::Origin::AlignedAlloc:
171+
return "free_aligned_sized";
172+
}
173+
return "<invalid origin>";
174+
}
175+
145176
// The deallocation function used is at odds with the one used to allocate the
146177
// chunk (eg: new[]/delete or malloc/delete, and so on).
147178
void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, const void *Ptr,
148-
u8 TypeA, u8 TypeB) {
179+
u8 TypeA, u8 TypeB,
180+
bool HasDeleteSize) {
149181
ScopedErrorReport Report;
150-
Report.append("allocation type mismatch when %s address %p (%d vs %d)\n",
151-
stringifyAction(Action), Ptr, TypeA, TypeB);
182+
Report.append(
183+
"allocation type mismatch when %s address %p (%s vs %s)\n",
184+
stringifyAction(Action), Ptr,
185+
stringifyAllocOrigin(static_cast<Chunk::Origin>(TypeA)),
186+
stringifyDeallocOrigin(static_cast<Chunk::Origin>(TypeB), HasDeleteSize));
152187
}
153188

154189
// The size specified to the delete operator does not match the one that was
@@ -161,6 +196,28 @@ void NORETURN reportDeleteSizeMismatch(const void *Ptr, uptr Size,
161196
Size, ExpectedSize);
162197
}
163198

199+
void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment) {
200+
ScopedErrorReport Report;
201+
Report.append(
202+
"invalid aligned delete when deallocating address %p (%zu vs %zu)\n", Ptr,
203+
getLeastSignificantSetBitIndex(reinterpret_cast<uptr>(Ptr)), Alignment);
204+
}
205+
206+
void NORETURN reportFreeSizeMismatch(const void *Ptr, uptr Size,
207+
uptr ExpectedSize) {
208+
ScopedErrorReport Report;
209+
Report.append(
210+
"invalid sized free when deallocating address %p (%zu vs %zu)\n", Ptr,
211+
Size, ExpectedSize);
212+
}
213+
214+
void NORETURN reportFreeAlignmentMismatch(const void *Ptr, uptr Alignment) {
215+
ScopedErrorReport Report;
216+
Report.append(
217+
"invalid aligned free when deallocating address %p (%zu vs %zu)\n", Ptr,
218+
getLeastSignificantSetBitIndex(reinterpret_cast<uptr>(Ptr)), Alignment);
219+
}
220+
164221
void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment) {
165222
ScopedErrorReport Report;
166223
Report.append(

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,9 +44,13 @@ enum class AllocatorAction : u8 {
4444
void NORETURN reportInvalidChunkState(AllocatorAction Action, const void *Ptr);
4545
void NORETURN reportMisalignedPointer(AllocatorAction Action, const void *Ptr);
4646
void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, const void *Ptr,
47-
u8 TypeA, u8 TypeB);
47+
u8 TypeA, u8 TypeB, bool HasDeleteSize);
4848
void NORETURN reportDeleteSizeMismatch(const void *Ptr, uptr Size,
4949
uptr ExpectedSize);
50+
void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment);
51+
void NORETURN reportFreeSizeMismatch(const void *Ptr, uptr Size,
52+
uptr ExpectedSize);
53+
void NORETURN reportFreeAlignmentMismatch(const void *Ptr, uptr Alignment);
5054

5155
// C wrappers errors.
5256
void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment);

compiler-rt/lib/scudo/standalone/tests/combined_test.cpp

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@ void ScudoCombinedTest<Config>::BasicTest(scudo::uptr SizeLog) {
327327
EXPECT_LE(Size, Allocator->getUsableSize(P));
328328
memset(P, 0xaa, Size);
329329
checkMemoryTaggingMaybe(Allocator, P, Size, Align);
330-
Allocator->deallocate(P, Origin, Size);
330+
Allocator->deallocate(P, Origin, Size, true);
331331
}
332332
}
333333

@@ -374,7 +374,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroContents) {
374374
for (scudo::uptr I = 0; I < Size; I++)
375375
ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
376376
memset(P, 0xaa, Size);
377-
Allocator->deallocate(P, Origin, Size);
377+
Allocator->deallocate(P, Origin, Size, true);
378378
}
379379
}
380380
}
@@ -392,7 +392,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroFill) {
392392
for (scudo::uptr I = 0; I < Size; I++)
393393
ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
394394
memset(P, 0xaa, Size);
395-
Allocator->deallocate(P, Origin, Size);
395+
Allocator->deallocate(P, Origin, Size, true);
396396
}
397397
}
398398
}
@@ -709,7 +709,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ThreadedCombined) {
709709

710710
while (!V.empty()) {
711711
auto Pair = V.back();
712-
Allocator->deallocate(Pair.first, Origin, Pair.second);
712+
Allocator->deallocate(Pair.first, Origin, Pair.second, true);
713713
V.pop_back();
714714
}
715715
});
@@ -782,26 +782,26 @@ TEST(ScudoCombinedDeathTest, DeathCombined) {
782782
EXPECT_NE(P, nullptr);
783783

784784
// Invalid sized deallocation.
785-
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), "");
785+
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U, true), "");
786786

787787
// Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available.
788788
UNUSED void *MisalignedP =
789789
reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) | 1U);
790-
EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), "");
791-
EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), "");
790+
EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size, true), "");
791+
EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U, true), "");
792792

793793
// Header corruption.
794794
scudo::u64 *H =
795795
reinterpret_cast<scudo::u64 *>(scudo::Chunk::getAtomicHeader(P));
796796
*H ^= 0x42U;
797-
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
797+
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), "");
798798
*H ^= 0x420042U;
799-
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
799+
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), "");
800800
*H ^= 0x420000U;
801801

802802
// Invalid chunk state.
803-
Allocator->deallocate(P, Origin, Size);
804-
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
803+
Allocator->deallocate(P, Origin, Size, true);
804+
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), "");
805805
EXPECT_DEATH(Allocator->reallocate(P, Size * 2U), "");
806806
EXPECT_DEATH(Allocator->getUsableSize(P), "");
807807
}
@@ -908,13 +908,13 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, DisableMemInit) {
908908
memset(Ptrs[I], 0xaa, Size);
909909
}
910910
for (unsigned I = 0; I != Ptrs.size(); ++I)
911-
Allocator->deallocate(Ptrs[I], Origin, Size);
911+
Allocator->deallocate(Ptrs[I], Origin, Size, true);
912912
for (unsigned I = 0; I != Ptrs.size(); ++I) {
913913
Ptrs[I] = Allocator->allocate(Size - 8, Origin);
914914
memset(Ptrs[I], 0xbb, Size - 8);
915915
}
916916
for (unsigned I = 0; I != Ptrs.size(); ++I)
917-
Allocator->deallocate(Ptrs[I], Origin, Size - 8);
917+
Allocator->deallocate(Ptrs[I], Origin, Size - 8, true);
918918
for (unsigned I = 0; I != Ptrs.size(); ++I) {
919919
Ptrs[I] = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);
920920
for (scudo::uptr J = 0; J < Size; ++J)

compiler-rt/lib/scudo/standalone/tests/report_test.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ TEST(ScudoReportDeathTest, Generic) {
4141
scudo::reportMisalignedPointer(scudo::AllocatorAction::Deallocating, P),
4242
"Scudo ERROR.*deallocating.*42424242");
4343
EXPECT_DEATH(scudo::reportDeallocTypeMismatch(
44-
scudo::AllocatorAction::Reallocating, P, 0, 1),
44+
scudo::AllocatorAction::Reallocating, P, 0, 1, false),
4545
"Scudo ERROR.*reallocating.*42424242");
4646
EXPECT_DEATH(scudo::reportDeleteSizeMismatch(P, 123, 456),
4747
"Scudo ERROR.*42424242.*123.*456");

0 commit comments

Comments
 (0)