[scudo] Add free_sized and free_aligned_sized (#186881)
Add one new flag, dealloc_align_mismatch that turns on/off alignment checks. Add three new config parameters, one for deallocate type mismatch (such as abort on new/free if true), one for checking if the size parameter matches on dealloc and one for checking if the alignment is correct on a dealloc. Add extra flags to be passed for to indicate to do an align/size check. Update report functions to better indicate the errors. Add unit tests for all of these. This is based on these upstream cls by jcking: https://github.com/llvm/llvm-project/pull/147735 https://github.com/llvm/llvm-project/pull/146556
This commit is contained in:
parent
66f06f54cb
commit
1b44e34b18
@ -68,6 +68,25 @@ BASE_OPTIONAL(const bool, ExactUsableSize, true)
|
||||
BASE_OPTIONAL(const bool, EnableZeroOnDealloc, false)
|
||||
#endif
|
||||
|
||||
// Abort if there is a mismatch between the function type that allocates memory
|
||||
// and the function type that deallocates memory.
|
||||
// These conditions will abort with an error message:
|
||||
// new/any C free operation
|
||||
// any C allocation operation/any delete operation
|
||||
// new/delete[]
|
||||
// new[]/delete
|
||||
// aligned alloc/free_sized
|
||||
// non-aligned alloc/free_aligned_sized
|
||||
BASE_OPTIONAL(const bool, AbortOnDeallocTypeMismatch, true)
|
||||
|
||||
// Abort if there is free function that takes a size value but that size
|
||||
// does not match the allocation size.
|
||||
BASE_OPTIONAL(const bool, AbortOnDeallocSizeMismatch, true)
|
||||
|
||||
// Abort if there is free function that takes an alignment value but that
|
||||
// alignment does not match the allocation alignment.
|
||||
BASE_OPTIONAL(const bool, AbortOnDeallocAlignmentMismatch, true)
|
||||
|
||||
// PRIMARY_REQUIRED_TYPE(NAME)
|
||||
//
|
||||
// SizeClassMap to use with the Primary.
|
||||
|
||||
@ -53,12 +53,29 @@ namespace Chunk {
|
||||
// but https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414 prevents it from
|
||||
// happening, as it will error, complaining the number of bits is not enough.
|
||||
enum Origin : u8 {
|
||||
Malloc = 0,
|
||||
New = 1,
|
||||
NewArray = 2,
|
||||
Memalign = 3,
|
||||
Malloc = 0, // malloc, calloc, realloc
|
||||
New = 1, // operator new
|
||||
NewArray = 2, // operator new []
|
||||
Memalign = 3, // aligned_alloc, memalign, posix_memalign, pvalloc, valloc
|
||||
|
||||
// These flags are not stored in the Origin in the header, used in deallocate
|
||||
// for verification purposes.
|
||||
Size = 0x10, // Verify size parameter.
|
||||
Align = 0x20, // Verify align parameter.
|
||||
|
||||
// NOTE: It is currently not possible to verify a new/new [] aligned
|
||||
// allocation calls delete/delete [] that is aligned due to only
|
||||
// having two bits to store the Origin in the header.
|
||||
};
|
||||
|
||||
ALWAYS_INLINE u8 originBaseType(u8 Origin) {
|
||||
return (Origin & 3) == Origin::Memalign ? Origin::Malloc : Origin & 0x3;
|
||||
}
|
||||
ALWAYS_INLINE bool originAligned(u8 Origin) {
|
||||
return Origin == Origin::Memalign || (Origin & Origin::Align);
|
||||
}
|
||||
ALWAYS_INLINE bool originSized(u8 Origin) { return Origin & Origin::Size; }
|
||||
|
||||
enum State : u8 { Available = 0, Allocated = 1, Quarantined = 2 };
|
||||
|
||||
typedef u64 PackedHeader;
|
||||
@ -71,6 +88,10 @@ struct UnpackedHeader {
|
||||
uptr SizeOrUnusedBytes : 20;
|
||||
uptr Offset : 16;
|
||||
uptr Checksum : 16;
|
||||
|
||||
ALWAYS_INLINE u8 getOrigin() { return OriginOrWasZeroed; }
|
||||
|
||||
ALWAYS_INLINE void setOrigin(u8 Origin) { OriginOrWasZeroed = Origin; }
|
||||
};
|
||||
typedef atomic_u64 AtomicPackedHeader;
|
||||
static_assert(sizeof(UnpackedHeader) == sizeof(PackedHeader), "");
|
||||
|
||||
@ -169,6 +169,8 @@ public:
|
||||
Primary.Options.setFillContentsMode(PatternOrZeroFill);
|
||||
if (getFlags()->dealloc_type_mismatch)
|
||||
Primary.Options.set(OptionBit::DeallocTypeMismatch);
|
||||
if (getFlags()->dealloc_align_mismatch)
|
||||
Primary.Options.set(OptionBit::DeallocAlignMismatch);
|
||||
if (getFlags()->delete_size_mismatch)
|
||||
Primary.Options.set(OptionBit::DeleteSizeMismatch);
|
||||
if (systemSupportsMemoryTagging())
|
||||
@ -442,8 +444,66 @@ public:
|
||||
SizeOrUnusedBytes, FillContents);
|
||||
}
|
||||
|
||||
NOINLINE void deallocate(void *Ptr, Chunk::Origin Origin, uptr DeleteSize = 0,
|
||||
UNUSED uptr Alignment = MinAlignment) {
|
||||
ALWAYS_INLINE void deallocate(void *Ptr, Chunk::Origin Origin) {
|
||||
deallocate(Ptr, Origin, /*DeleteSize=*/0, /*DeleteAlignment=*/0);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void deallocateSized(void *Ptr, Chunk::Origin Origin,
|
||||
uptr DeleteSize) {
|
||||
deallocate(Ptr, Origin | Chunk::Origin::Size, DeleteSize,
|
||||
/*DeleteAlignment=*/0);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void deallocateSizedAligned(void *Ptr, Chunk::Origin Origin,
|
||||
uptr DeleteSize,
|
||||
uptr DeleteAlignment) {
|
||||
deallocate(Ptr, Origin | Chunk::Origin::Size | Chunk::Origin::Align,
|
||||
DeleteSize, DeleteAlignment);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void deallocateAligned(void *Ptr, Chunk::Origin Origin,
|
||||
uptr DeleteAlignment) {
|
||||
deallocate(Ptr, Origin | Chunk::Origin::Align,
|
||||
/*DeleteSize=*/0, /*DeleteAlignment=*/DeleteAlignment);
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void checkSizeMatch(const void *Ptr,
|
||||
Chunk::UnpackedHeader *Header, uptr Size,
|
||||
uptr DeallocSize) {
|
||||
if (AllocatorConfig::getExactUsableSize()) {
|
||||
if (DeallocSize != Size)
|
||||
reportDeleteSizeMismatch(Ptr, DeallocSize, Size);
|
||||
} else if (DeallocSize != Size && DeallocSize != getUsableSize(Ptr, Header))
|
||||
reportDeleteSizeMismatch(Ptr, DeallocSize, Size,
|
||||
getUsableSize(Ptr, Header));
|
||||
}
|
||||
|
||||
ALWAYS_INLINE void checkTypeMatch(AllocatorAction Action, const void *Ptr,
|
||||
u8 AllocOrigin, u8 DeallocOrigin) {
|
||||
if (UNLIKELY(Chunk::originBaseType(AllocOrigin) !=
|
||||
Chunk::originBaseType(DeallocOrigin)))
|
||||
reportDeallocTypeMismatch(Action, Ptr, AllocOrigin, DeallocOrigin);
|
||||
|
||||
// There is no way to store that a new/new [] did an aligned allocate,
|
||||
// so skip that part of the verification.
|
||||
if (UNLIKELY(AllocOrigin == Chunk::Origin::New ||
|
||||
AllocOrigin == Chunk::Origin::NewArray))
|
||||
return;
|
||||
|
||||
if (Chunk::originAligned(AllocOrigin)) {
|
||||
// Only disallow an aligned allocation and a non-aligned deallocation
|
||||
// if this is a realloc.
|
||||
if (Action == AllocatorAction::Reallocating &&
|
||||
!Chunk::originAligned(DeallocOrigin))
|
||||
reportDeallocTypeMismatch(Action, Ptr, AllocOrigin, DeallocOrigin);
|
||||
} else if (Chunk::originAligned(DeallocOrigin)) {
|
||||
// Origin not aligned, dealloc aligned.
|
||||
reportDeallocTypeMismatch(Action, Ptr, AllocOrigin, DeallocOrigin);
|
||||
}
|
||||
}
|
||||
|
||||
NOINLINE void deallocate(void *Ptr, u8 DeallocOrigin, uptr DeleteSize,
|
||||
uptr DeleteAlignment) {
|
||||
if (UNLIKELY(!Ptr))
|
||||
return;
|
||||
|
||||
@ -469,6 +529,10 @@ public:
|
||||
if (UNLIKELY(!isAligned(reinterpret_cast<uptr>(Ptr), MinAlignment)))
|
||||
reportMisalignedPointer(AllocatorAction::Deallocating, Ptr);
|
||||
|
||||
if (UNLIKELY(Chunk::originAligned(DeallocOrigin) &&
|
||||
!isPowerOfTwo(DeleteAlignment)))
|
||||
reportAlignmentNotPowerOfTwo(DeleteAlignment);
|
||||
|
||||
void *TaggedPtr = Ptr;
|
||||
Ptr = getHeaderTaggedPointer(Ptr);
|
||||
|
||||
@ -479,21 +543,22 @@ public:
|
||||
reportInvalidChunkState(AllocatorAction::Deallocating, Ptr);
|
||||
|
||||
const Options Options = Primary.Options.load();
|
||||
if (Options.get(OptionBit::DeallocTypeMismatch)) {
|
||||
if (UNLIKELY(Header.OriginOrWasZeroed != Origin)) {
|
||||
// With the exception of memalign'd chunks, that can be still be free'd.
|
||||
if (Header.OriginOrWasZeroed != Chunk::Origin::Memalign ||
|
||||
Origin != Chunk::Origin::Malloc)
|
||||
reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr,
|
||||
Header.OriginOrWasZeroed, Origin);
|
||||
}
|
||||
}
|
||||
|
||||
const uptr Size = getSize(Ptr, &Header);
|
||||
if (DeleteSize && Options.get(OptionBit::DeleteSizeMismatch)) {
|
||||
if (UNLIKELY(DeleteSize != Size))
|
||||
reportDeleteSizeMismatch(Ptr, DeleteSize, Size);
|
||||
}
|
||||
if (AllocatorConfig::getAbortOnDeallocSizeMismatch() &&
|
||||
Chunk::originSized(DeallocOrigin) &&
|
||||
Options.get(OptionBit::DeleteSizeMismatch))
|
||||
checkSizeMatch(Ptr, &Header, Size, DeleteSize);
|
||||
|
||||
if (AllocatorConfig::getAbortOnDeallocTypeMismatch() &&
|
||||
Options.get(OptionBit::DeallocTypeMismatch))
|
||||
checkTypeMatch(AllocatorAction::Deallocating, Ptr, Header.getOrigin(),
|
||||
DeallocOrigin);
|
||||
|
||||
if (UNLIKELY(AllocatorConfig::getAbortOnDeallocAlignmentMismatch() &&
|
||||
Chunk::originAligned(DeallocOrigin) &&
|
||||
Options.get(OptionBit::DeallocAlignMismatch) &&
|
||||
!isAligned(reinterpret_cast<uptr>(Ptr), DeleteAlignment)))
|
||||
reportDeleteAlignmentMismatch(Ptr, DeleteAlignment);
|
||||
|
||||
quarantineOrDeallocateChunk(Options, TaggedPtr, &Header, Size);
|
||||
}
|
||||
@ -542,12 +607,10 @@ public:
|
||||
// Pointer has to be allocated with a malloc-type function. Some
|
||||
// applications think that it is OK to realloc a memalign'ed pointer, which
|
||||
// will trigger this check. It really isn't.
|
||||
if (Options.get(OptionBit::DeallocTypeMismatch)) {
|
||||
if (UNLIKELY(Header.OriginOrWasZeroed != Chunk::Origin::Malloc))
|
||||
reportDeallocTypeMismatch(AllocatorAction::Reallocating, OldPtr,
|
||||
Header.OriginOrWasZeroed,
|
||||
Chunk::Origin::Malloc);
|
||||
}
|
||||
if (AllocatorConfig::getAbortOnDeallocTypeMismatch() &&
|
||||
Options.get(OptionBit::DeallocTypeMismatch))
|
||||
checkTypeMatch(AllocatorAction::Reallocating, OldPtr, Header.getOrigin(),
|
||||
Chunk::Origin::Malloc);
|
||||
|
||||
void *BlockBegin = getBlockBegin(OldTaggedPtr, &Header);
|
||||
uptr BlockEnd;
|
||||
@ -1175,7 +1238,7 @@ private:
|
||||
|
||||
Header.ClassId = ClassId & Chunk::ClassIdMask;
|
||||
Header.State = Chunk::State::Allocated;
|
||||
Header.OriginOrWasZeroed = Origin & Chunk::OriginMask;
|
||||
Header.setOrigin(Origin);
|
||||
Header.SizeOrUnusedBytes = SizeOrUnusedBytes & Chunk::SizeOrUnusedBytesMask;
|
||||
Chunk::storeHeader(Cookie, reinterpret_cast<void *>(addHeaderTag(UserPtr)),
|
||||
&Header);
|
||||
@ -1307,7 +1370,7 @@ private:
|
||||
|
||||
Header.ClassId = ClassId & Chunk::ClassIdMask;
|
||||
Header.State = Chunk::State::Allocated;
|
||||
Header.OriginOrWasZeroed = Origin & Chunk::OriginMask;
|
||||
Header.setOrigin(Origin);
|
||||
Header.SizeOrUnusedBytes = SizeOrUnusedBytes & Chunk::SizeOrUnusedBytesMask;
|
||||
Chunk::storeHeader(Cookie, Ptr, &Header);
|
||||
|
||||
|
||||
@ -32,6 +32,11 @@ SCUDO_FLAG(bool, delete_size_mismatch, true,
|
||||
"Terminate on a size mismatch between a sized-delete and the actual "
|
||||
"size of a chunk (as provided to new/new[]).")
|
||||
|
||||
SCUDO_FLAG(
|
||||
bool, dealloc_align_mismatch, true,
|
||||
"Terminate on an alignment mismatch in allocation-deallocation functions, "
|
||||
"eg: memalign/free_aligned_sized, etc.")
|
||||
|
||||
#if SCUDO_FUCHSIA
|
||||
SCUDO_FLAG(int, zero_on_dealloc_max_size, 0,
|
||||
"Only chunks smaller or equal to this threshold will be zeroed on "
|
||||
|
||||
@ -19,6 +19,7 @@ enum class OptionBit {
|
||||
MayReturnNull,
|
||||
FillContents0of2,
|
||||
FillContents1of2,
|
||||
DeallocAlignMismatch,
|
||||
DeallocTypeMismatch,
|
||||
DeleteSizeMismatch,
|
||||
TrackAllocationStacks,
|
||||
|
||||
@ -128,6 +128,33 @@ static const char *stringifyAction(AllocatorAction Action) {
|
||||
return "<invalid action>";
|
||||
}
|
||||
|
||||
static const char *stringifyOrigin(u8 Origin) {
|
||||
switch (Chunk::originBaseType(Origin)) {
|
||||
case Chunk::Origin::Malloc:
|
||||
return "malloc";
|
||||
case Chunk::Origin::New:
|
||||
return "new";
|
||||
case Chunk::Origin::NewArray:
|
||||
return "new[]";
|
||||
case Chunk::Origin::Memalign:
|
||||
return "memalign";
|
||||
default:
|
||||
return "<invalid origin>";
|
||||
}
|
||||
}
|
||||
|
||||
static const char *stringifyOriginFlags(u8 Origin) {
|
||||
bool Sized = Chunk::originSized(Origin);
|
||||
bool Aligned = Chunk::originAligned(Origin);
|
||||
if (Sized && Aligned)
|
||||
return "sized aligned ";
|
||||
else if (Sized)
|
||||
return "sized ";
|
||||
else if (Aligned)
|
||||
return "aligned ";
|
||||
return "";
|
||||
}
|
||||
|
||||
// The chunk is not in a state congruent with the operation we want to perform.
|
||||
// This is usually the case with a double-free, a realloc of a freed pointer.
|
||||
void NORETURN reportInvalidChunkState(AllocatorAction Action, const void *Ptr) {
|
||||
@ -145,20 +172,35 @@ void NORETURN reportMisalignedPointer(AllocatorAction Action, const void *Ptr) {
|
||||
// The deallocation function used is at odds with the one used to allocate the
|
||||
// chunk (eg: new[]/delete or malloc/delete, and so on).
|
||||
void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, const void *Ptr,
|
||||
u8 TypeA, u8 TypeB) {
|
||||
u8 AllocOrigin, u8 DeallocOrigin) {
|
||||
ScopedErrorReport Report;
|
||||
Report.append("allocation type mismatch when %s address %p (%d vs %d)\n",
|
||||
stringifyAction(Action), Ptr, TypeA, TypeB);
|
||||
Report.append("allocation type mismatch when %s address %p (%s%s vs %s%s)\n",
|
||||
stringifyAction(Action), Ptr, stringifyOriginFlags(AllocOrigin),
|
||||
stringifyOrigin(AllocOrigin),
|
||||
stringifyOriginFlags(DeallocOrigin),
|
||||
stringifyOrigin(DeallocOrigin));
|
||||
}
|
||||
|
||||
// The size specified to the delete operator does not match the one that was
|
||||
// passed to new when allocating the chunk.
|
||||
void NORETURN reportDeleteSizeMismatch(const void *Ptr, uptr Size,
|
||||
uptr ExpectedSize) {
|
||||
uptr ExpectedSize,
|
||||
uptr ExpectedUsableSize) {
|
||||
ScopedErrorReport Report;
|
||||
Report.append(
|
||||
"invalid sized delete when deallocating address %p (%zu vs %zu)\n", Ptr,
|
||||
Size, ExpectedSize);
|
||||
Report.append("invalid sized delete when deallocating address %p (%zu vs %zu",
|
||||
Ptr, Size, ExpectedSize);
|
||||
if (ExpectedUsableSize != 0)
|
||||
Report.append(" or %zu", ExpectedUsableSize);
|
||||
Report.append(")\n");
|
||||
}
|
||||
|
||||
void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment) {
|
||||
ScopedErrorReport Report;
|
||||
Report.append("invalid aligned delete when deallocating address %p (%zu bit "
|
||||
"align vs %zu bit align)\n",
|
||||
Ptr,
|
||||
getLeastSignificantSetBitIndex(reinterpret_cast<uptr>(Ptr)),
|
||||
getLeastSignificantSetBitIndex(Alignment));
|
||||
}
|
||||
|
||||
void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment) {
|
||||
|
||||
@ -44,9 +44,11 @@ enum class AllocatorAction : u8 {
|
||||
void NORETURN reportInvalidChunkState(AllocatorAction Action, const void *Ptr);
|
||||
void NORETURN reportMisalignedPointer(AllocatorAction Action, const void *Ptr);
|
||||
void NORETURN reportDeallocTypeMismatch(AllocatorAction Action, const void *Ptr,
|
||||
u8 TypeA, u8 TypeB);
|
||||
u8 AllocOrigin, u8 DeallocOrigin);
|
||||
void NORETURN reportDeleteSizeMismatch(const void *Ptr, uptr Size,
|
||||
uptr ExpectedSize);
|
||||
uptr ExpectedSize,
|
||||
uptr ExpectedUsableSize = 0);
|
||||
void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment);
|
||||
|
||||
// C wrappers errors.
|
||||
void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment);
|
||||
|
||||
@ -333,7 +333,7 @@ void ScudoCombinedTest<Config>::BasicTest(scudo::uptr SizeLog) {
|
||||
EXPECT_LE(Size, Allocator->getUsableSize(P));
|
||||
memset(P, 0xaa, Size);
|
||||
checkMemoryTaggingMaybe(Allocator, P, Size, Align);
|
||||
Allocator->deallocate(P, Origin, Size);
|
||||
Allocator->deallocateSized(P, Origin, Size);
|
||||
}
|
||||
}
|
||||
|
||||
@ -382,7 +382,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroContents) {
|
||||
for (scudo::uptr I = 0; I < Size; I++)
|
||||
ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
|
||||
memset(P, 0xaa, Size);
|
||||
Allocator->deallocate(P, Origin, Size);
|
||||
Allocator->deallocateSized(P, Origin, Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -400,7 +400,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroFill) {
|
||||
for (scudo::uptr I = 0; I < Size; I++)
|
||||
ASSERT_EQ((reinterpret_cast<char *>(P))[I], '\0');
|
||||
memset(P, 0xaa, Size);
|
||||
Allocator->deallocate(P, Origin, Size);
|
||||
Allocator->deallocateSized(P, Origin, Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -427,7 +427,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, PatternOrZeroFill) {
|
||||
ASSERT_TRUE(V == scudo::PatternFillByte || V == 0);
|
||||
}
|
||||
memset(P, 0xaa, Size);
|
||||
Allocator->deallocate(P, Origin, Size);
|
||||
Allocator->deallocateSized(P, Origin, Size);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -712,7 +712,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ThreadedCombined) {
|
||||
|
||||
while (!V.empty()) {
|
||||
auto Pair = V.back();
|
||||
Allocator->deallocate(Pair.first, Origin, Pair.second);
|
||||
Allocator->deallocateSized(Pair.first, Origin, Pair.second);
|
||||
V.pop_back();
|
||||
}
|
||||
});
|
||||
@ -796,26 +796,26 @@ TEST(ScudoCombinedDeathTest, DeathCombined) {
|
||||
EXPECT_NE(P, nullptr);
|
||||
|
||||
// Invalid sized deallocation.
|
||||
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), "");
|
||||
EXPECT_DEATH(Allocator->deallocateSized(P, Origin, Size + 8U), "");
|
||||
|
||||
// Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available.
|
||||
UNUSED void *MisalignedP =
|
||||
reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(P) | 1U);
|
||||
EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), "");
|
||||
EXPECT_DEATH(Allocator->deallocateSized(MisalignedP, Origin, Size), "");
|
||||
EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), "");
|
||||
|
||||
// Header corruption.
|
||||
scudo::u64 *H =
|
||||
reinterpret_cast<scudo::u64 *>(scudo::Chunk::getAtomicHeader(P));
|
||||
*H ^= 0x42U;
|
||||
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
|
||||
EXPECT_DEATH(Allocator->deallocateSized(P, Origin, Size), "");
|
||||
*H ^= 0x420042U;
|
||||
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
|
||||
EXPECT_DEATH(Allocator->deallocateSized(P, Origin, Size), "");
|
||||
*H ^= 0x420000U;
|
||||
|
||||
// Invalid chunk state.
|
||||
Allocator->deallocate(P, Origin, Size);
|
||||
EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), "");
|
||||
Allocator->deallocateSized(P, Origin, Size);
|
||||
EXPECT_DEATH(Allocator->deallocateSized(P, Origin, Size), "");
|
||||
EXPECT_DEATH(Allocator->reallocate(P, Size * 2U), "");
|
||||
EXPECT_DEATH(Allocator->getUsableSize(P), "");
|
||||
}
|
||||
@ -922,13 +922,13 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, DisableMemInit) {
|
||||
memset(Ptrs[I], 0xaa, Size);
|
||||
}
|
||||
for (unsigned I = 0; I != Ptrs.size(); ++I)
|
||||
Allocator->deallocate(Ptrs[I], Origin, Size);
|
||||
Allocator->deallocateSized(Ptrs[I], Origin, Size);
|
||||
for (unsigned I = 0; I != Ptrs.size(); ++I) {
|
||||
Ptrs[I] = Allocator->allocate(Size - 8, Origin);
|
||||
memset(Ptrs[I], 0xbb, Size - 8);
|
||||
}
|
||||
for (unsigned I = 0; I != Ptrs.size(); ++I)
|
||||
Allocator->deallocate(Ptrs[I], Origin, Size - 8);
|
||||
Allocator->deallocateSized(Ptrs[I], Origin, Size - 8);
|
||||
for (unsigned I = 0; I != Ptrs.size(); ++I) {
|
||||
Ptrs[I] = Allocator->allocate(Size, Origin, 1U << MinAlignLog, true);
|
||||
for (scudo::uptr J = 0; J < Size; ++J)
|
||||
@ -1606,3 +1606,272 @@ TEST(ScudoCombinedTest, StressThreadInitTSDExclusive) {
|
||||
for (size_t I = 0; I < 10; I++)
|
||||
RunStress<AllocatorT>();
|
||||
}
|
||||
|
||||
struct TestMatchConfig {
|
||||
static const bool MaySupportMemoryTagging = false;
|
||||
template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U, 1U>;
|
||||
|
||||
struct Primary {
|
||||
using SizeClassMap = scudo::AndroidSizeClassMap;
|
||||
#if SCUDO_CAN_USE_PRIMARY64
|
||||
static const scudo::uptr RegionSizeLog = 28U;
|
||||
typedef scudo::u32 CompactPtrT;
|
||||
static const scudo::uptr CompactPtrScale = SCUDO_MIN_ALIGNMENT_LOG;
|
||||
static const scudo::uptr GroupSizeLog = 20U;
|
||||
static const bool EnableRandomOffset = true;
|
||||
static const scudo::uptr MapSizeIncrement = 1UL << 18;
|
||||
#else
|
||||
static const scudo::uptr RegionSizeLog = 18U;
|
||||
static const scudo::uptr GroupSizeLog = 18U;
|
||||
typedef scudo::uptr CompactPtrT;
|
||||
#endif
|
||||
static const bool EnableBlockCache = false;
|
||||
static const scudo::s32 MinReleaseToOsIntervalMs = 1000;
|
||||
static const scudo::s32 MaxReleaseToOsIntervalMs = 1000;
|
||||
};
|
||||
|
||||
#if SCUDO_CAN_USE_PRIMARY64
|
||||
template <typename Config>
|
||||
using PrimaryT = scudo::SizeClassAllocator64<Config>;
|
||||
#else
|
||||
template <typename Config>
|
||||
using PrimaryT = scudo::SizeClassAllocator32<Config>;
|
||||
#endif
|
||||
|
||||
template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;
|
||||
struct Secondary {
|
||||
template <typename Config>
|
||||
using CacheT = scudo::MapAllocatorNoCache<Config>;
|
||||
};
|
||||
};
|
||||
|
||||
TEST(ScudoCombinedDeathTest, DeallocateAlignNotPowerOfTwo) {
|
||||
using AllocatorT = scudo::Allocator<TestMatchConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Memalign, 32);
|
||||
EXPECT_DEATH(
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::Memalign, 9),
|
||||
"alignment must be a power of two");
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::Memalign, 32);
|
||||
}
|
||||
|
||||
TEST(ScudoCombinedDeathTest, TypeMismatch) {
|
||||
ScopedScudoOptions Options("dealloc_type_mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Malloc);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->deallocate(Ptr, scudo::Chunk::Origin::New),
|
||||
"deallocating.*\\(malloc vs new\\)");
|
||||
EXPECT_DEATH(Allocator->deallocate(Ptr, scudo::Chunk::Origin::NewArray),
|
||||
"deallocating.*\\(malloc vs new\\[\\]\\)");
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::Malloc);
|
||||
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::New);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->deallocate(Ptr, scudo::Chunk::Origin::Malloc),
|
||||
"deallocating.*\\(new vs malloc\\)");
|
||||
EXPECT_DEATH(Allocator->deallocate(Ptr, scudo::Chunk::Origin::NewArray),
|
||||
"deallocating.*\\(new vs new\\[\\]\\)");
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::New);
|
||||
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::NewArray);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->deallocate(Ptr, scudo::Chunk::Origin::Malloc),
|
||||
"deallocating.*\\(new\\[\\] vs malloc\\)");
|
||||
EXPECT_DEATH(Allocator->deallocate(Ptr, scudo::Chunk::Origin::New),
|
||||
"deallocating.*\\(new\\[\\] vs new\\)");
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::NewArray);
|
||||
}
|
||||
|
||||
TEST(ScudoCombinedDeathTest, ReallocTypeMismatch) {
|
||||
ScopedScudoOptions Options("dealloc_type_mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::New);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->reallocate(Ptr, 1000000),
|
||||
"reallocating.*\\(new vs malloc\\)");
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::New);
|
||||
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::NewArray);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->reallocate(Ptr, 1000000),
|
||||
"reallocating.*\\(new\\[\\] vs malloc\\)");
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::NewArray);
|
||||
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Memalign, 32);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->reallocate(Ptr, 1000000),
|
||||
"reallocating.*\\(aligned malloc vs malloc\\)");
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::Memalign, 32);
|
||||
}
|
||||
|
||||
TEST(ScudoCombinedDeathTest, AlignTypeMismatch) {
|
||||
ScopedScudoOptions Options("dealloc_type_mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Memalign, 32);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->reallocate(Ptr, 1000),
|
||||
"reallocating.*\\(aligned malloc vs malloc\\)");
|
||||
// Aligned allocate with non-aligned deallocate is okay.
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::Malloc);
|
||||
}
|
||||
|
||||
// Scudo currently cannot verify that a pointer allocated with an aligned
|
||||
// new/new [] is deallocated with an aligned delete/delete [].
|
||||
TEST(ScudoCombinedTest, NewType) {
|
||||
ScopedScudoOptions Options("dealloc_type_mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::New, 1024);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::New, 1024);
|
||||
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::NewArray, 1024);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::NewArray, 1024);
|
||||
}
|
||||
|
||||
TEST(ScudoCombinedDeathTest, SizeMismatch) {
|
||||
ScopedScudoOptions Options("delete_size_mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Malloc);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(
|
||||
Allocator->deallocateSized(Ptr, scudo::Chunk::Origin::Malloc, 1000000),
|
||||
"invalid sized delete when deallocating.*\\(1000000 vs 10\\)");
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::Malloc);
|
||||
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Memalign, 32);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(Allocator->deallocateSizedAligned(
|
||||
Ptr, scudo::Chunk::Origin::Malloc, 1000000, 32),
|
||||
"invalid sized delete when deallocating.*\\(1000000 vs 10\\)");
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::Malloc, 32);
|
||||
}
|
||||
|
||||
struct TestMatchNotExactUsableSizeConfig : public TestMatchConfig {
|
||||
static const bool ExactUsableSize = false;
|
||||
};
|
||||
|
||||
TEST(ScudoCombinedDeathTest, SizeNotExactUsableMismatch) {
|
||||
ScopedScudoOptions Options("delete_size_mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchNotExactUsableSizeConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Malloc);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(
|
||||
Allocator->deallocateSized(Ptr, scudo::Chunk::Origin::Malloc, 1000000),
|
||||
"invalid sized delete when deallocating.*\\(1000000 vs 10 or .*\\)");
|
||||
Allocator->deallocateSized(Ptr, scudo::Chunk::Origin::Malloc, 10);
|
||||
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Memalign, 32);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
EXPECT_DEATH(
|
||||
Allocator->deallocateSizedAligned(Ptr, scudo::Chunk::Origin::Malloc,
|
||||
1000000, 32),
|
||||
"invalid sized delete when deallocating.*\\(1000000 vs 10 or .*\\)");
|
||||
EXPECT_NE(10U, Allocator->getUsableSize(Ptr));
|
||||
Allocator->deallocateSizedAligned(Ptr, scudo::Chunk::Origin::Malloc,
|
||||
Allocator->getUsableSize(Ptr), 32);
|
||||
}
|
||||
|
||||
template <class AllocatorT>
|
||||
static void *getMinAlignedPointer(AllocatorT *Allocator) {
|
||||
// The actual alignment is not stored in the header, so the only check
|
||||
// that can be made is to verify how the pointer is aligned.
|
||||
// Therefore, try and allocate a pointer which has a low alignment but
|
||||
// not a high alignment so we can check for death.
|
||||
std::vector<void *> Ptrs;
|
||||
void *AlignedPtr = nullptr;
|
||||
scudo::uptr AlignmentMask = 2 * scudo::getPageSizeCached() - 1;
|
||||
for (size_t I = 0; I < 1000; ++I) {
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Memalign, 32);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
scudo::uptr FlatPtr = reinterpret_cast<scudo::uptr>(Ptr);
|
||||
if ((FlatPtr & AlignmentMask) != 0) {
|
||||
AlignedPtr = Ptr;
|
||||
break;
|
||||
}
|
||||
Ptrs.push_back(Ptr);
|
||||
}
|
||||
|
||||
// Free all of the other pointers.
|
||||
for (auto Ptr : Ptrs) {
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::Memalign, 32);
|
||||
}
|
||||
|
||||
return AlignedPtr;
|
||||
}
|
||||
|
||||
TEST(ScudoCombinedDeathTest, AlignMismatch) {
|
||||
ScopedScudoOptions Options("dealloc_align_mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
// Pointer is guaranteed to not be aligned to 2 * page size.
|
||||
void *AlignedPtr = getMinAlignedPointer<AllocatorT>(Allocator.get());
|
||||
if (AlignedPtr == nullptr) {
|
||||
GTEST_SKIP() << "Cannot allocate aligned pointer for test.";
|
||||
}
|
||||
|
||||
scudo::uptr Alignment = 2 * scudo::getPageSizeCached();
|
||||
EXPECT_DEATH(Allocator->deallocateAligned(
|
||||
AlignedPtr, scudo::Chunk::Origin::Malloc, Alignment),
|
||||
"invalid aligned delete when deallocating");
|
||||
EXPECT_DEATH(Allocator->deallocateSizedAligned(
|
||||
AlignedPtr, scudo::Chunk::Origin::Malloc, 10, Alignment),
|
||||
"invalid aligned delete when deallocating");
|
||||
|
||||
Allocator->deallocateAligned(AlignedPtr, scudo::Chunk::Origin::Malloc, 32);
|
||||
}
|
||||
|
||||
struct TestMatchOverrideConfig : public TestMatchConfig {
|
||||
// Disable all type/size/alignment checks.
|
||||
static const bool AbortOnDeallocTypeMismatch = false;
|
||||
static const bool AbortOnDeallocSizeMismatch = false;
|
||||
static const bool AbortOnDeallocAlignmentMismatch = false;
|
||||
};
|
||||
|
||||
TEST(ScudoCombinedTest, VerifyConfigOverrideMatchChecks) {
|
||||
ScopedScudoOptions Options(
|
||||
"dealloc_type_mismatch=true:delete_size_mismatch=true:dealloc_align_"
|
||||
"mismatch=true");
|
||||
|
||||
using AllocatorT = scudo::Allocator<TestMatchOverrideConfig>;
|
||||
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
|
||||
|
||||
// Verify type mismatch is ignored.
|
||||
void *Ptr = Allocator->allocate(10, scudo::Chunk::Origin::New);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
Allocator->deallocate(Ptr, scudo::Chunk::Origin::Malloc);
|
||||
|
||||
// Verify size mismatch is ignored.
|
||||
Ptr = Allocator->allocate(10, scudo::Chunk::Origin::Malloc);
|
||||
EXPECT_TRUE(Ptr != nullptr);
|
||||
Allocator->deallocateSized(Ptr, scudo::Chunk::Origin::Malloc, 1000000);
|
||||
|
||||
// Pointer is guaranteed to not be aligned to 2 * page size.
|
||||
scudo::uptr Alignment = 2 * scudo::getPageSizeCached();
|
||||
Ptr = getMinAlignedPointer<AllocatorT>(Allocator.get());
|
||||
if (Ptr != nullptr) {
|
||||
Allocator->deallocateAligned(Ptr, scudo::Chunk::Origin::Malloc, Alignment);
|
||||
}
|
||||
}
|
||||
|
||||
@ -110,11 +110,14 @@ TEST(ScudoFlagsTest, AllocatorFlags) {
|
||||
Flags.setDefaults();
|
||||
Flags.dealloc_type_mismatch = false;
|
||||
Flags.delete_size_mismatch = false;
|
||||
Flags.dealloc_align_mismatch = false;
|
||||
Flags.quarantine_max_chunk_size = 1024;
|
||||
Parser.parseString("dealloc_type_mismatch=true:delete_size_mismatch=true:"
|
||||
"quarantine_max_chunk_size=2048");
|
||||
Parser.parseString(
|
||||
"dealloc_type_mismatch=true:delete_size_mismatch=true:"
|
||||
"dealloc_align_mismatch=true:quarantine_max_chunk_size=2048");
|
||||
EXPECT_TRUE(Flags.dealloc_type_mismatch);
|
||||
EXPECT_TRUE(Flags.delete_size_mismatch);
|
||||
EXPECT_TRUE(Flags.dealloc_align_mismatch);
|
||||
EXPECT_EQ(2048, Flags.quarantine_max_chunk_size);
|
||||
}
|
||||
|
||||
|
||||
@ -40,11 +40,30 @@ TEST(ScudoReportDeathTest, Generic) {
|
||||
EXPECT_DEATH(
|
||||
scudo::reportMisalignedPointer(scudo::AllocatorAction::Deallocating, P),
|
||||
"Scudo ERROR.*deallocating.*42424242");
|
||||
EXPECT_DEATH(
|
||||
scudo::reportDeallocTypeMismatch(scudo::AllocatorAction::Reallocating, P,
|
||||
scudo::Chunk::Origin::New,
|
||||
scudo::Chunk::Origin::Memalign),
|
||||
"Scudo ERROR.*reallocating.*42424242.*\\(new vs aligned malloc\\)");
|
||||
EXPECT_DEATH(scudo::reportDeallocTypeMismatch(
|
||||
scudo::AllocatorAction::Reallocating, P, 0, 1),
|
||||
"Scudo ERROR.*reallocating.*42424242");
|
||||
scudo::AllocatorAction::Deallocating, P,
|
||||
scudo::Chunk::Origin::Memalign, scudo::Chunk::Origin::New),
|
||||
"Scudo ERROR.*deallocating.*\\(aligned malloc vs new\\)");
|
||||
EXPECT_DEATH(scudo::reportDeallocTypeMismatch(
|
||||
scudo::AllocatorAction::Deallocating, P,
|
||||
scudo::Chunk::Origin::Malloc | scudo::Chunk::Origin::Size,
|
||||
scudo::Chunk::Origin::NewArray | scudo::Chunk::Origin::Size |
|
||||
scudo::Chunk::Origin::Align),
|
||||
"Scudo ERROR.*deallocating.*\\(sized malloc vs sized aligned "
|
||||
"new\\[\\]\\)");
|
||||
EXPECT_DEATH(scudo::reportDeleteSizeMismatch(P, 123, 456),
|
||||
"Scudo ERROR.*42424242.*123.*456");
|
||||
"Scudo ERROR.*42424242.*\\(123 vs 456\\)");
|
||||
EXPECT_DEATH(scudo::reportDeleteSizeMismatch(P, 123, 456, 789),
|
||||
"Scudo ERROR.*42424242.*\\(123 vs 456 or 789\\)");
|
||||
EXPECT_DEATH(
|
||||
scudo::reportDeleteAlignmentMismatch(reinterpret_cast<void *>(0x80),
|
||||
0x100),
|
||||
"Scudo ERROR.*invalid aligned delete.*\\(7 bit align vs 8 bit align\\)");
|
||||
}
|
||||
|
||||
TEST(ScudoReportDeathTest, CSpecific) {
|
||||
|
||||
@ -9,14 +9,6 @@
|
||||
#include "memtag.h"
|
||||
#include "tests/scudo_unit_test.h"
|
||||
|
||||
// Match Android's default configuration, which disables Scudo's mismatch
|
||||
// allocation check, as it is being triggered by some third party code.
|
||||
#if SCUDO_ANDROID
|
||||
#define DEALLOC_TYPE_MISMATCH "false"
|
||||
#else
|
||||
#define DEALLOC_TYPE_MISMATCH "true"
|
||||
#endif
|
||||
|
||||
static void EnableMemoryTaggingIfSupported() {
|
||||
if (!scudo::archSupportsMemoryTagging())
|
||||
return;
|
||||
@ -39,10 +31,12 @@ __scudo_default_options() {
|
||||
// will disable the feature entirely.
|
||||
EnableMemoryTaggingIfSupported();
|
||||
if (!UseQuarantine)
|
||||
return "dealloc_type_mismatch=" DEALLOC_TYPE_MISMATCH;
|
||||
return "dealloc_type_mismatch=true:delete_size_mismatch=true:dealloc_align_"
|
||||
"mismatch=true";
|
||||
return "quarantine_size_kb=256:thread_local_quarantine_size_kb=128:"
|
||||
"quarantine_max_chunk_size=512:"
|
||||
"dealloc_type_mismatch=" DEALLOC_TYPE_MISMATCH;
|
||||
"dealloc_type_mismatch=true:delete_size_mismatch=true:dealloc_align_"
|
||||
"mismatch=true";
|
||||
}
|
||||
|
||||
#if !defined(SCUDO_NO_TEST_MAIN)
|
||||
|
||||
@ -25,16 +25,35 @@
|
||||
#if SCUDO_FUCHSIA
|
||||
// Fuchsia only has valloc
|
||||
#define HAVE_VALLOC 1
|
||||
|
||||
#define VERIFY_FREE_SIZED_DEATH 1
|
||||
#define VERIFY_FREE_ALIGNED_SIZED_DEATH 1
|
||||
#elif SCUDO_ANDROID
|
||||
// Android only has pvalloc/valloc on 32 bit
|
||||
#if !defined(__LP64__)
|
||||
#define HAVE_PVALLOC 1
|
||||
#define HAVE_VALLOC 1
|
||||
#endif // !defined(__LP64__)
|
||||
|
||||
// Disable all size/aligned checks since they are disabled on Android.
|
||||
#define VERIFY_FREE_SIZED_DEATH 0
|
||||
#define VERIFY_FREE_ALIGNED_SIZED_DEATH 0
|
||||
#else
|
||||
// All others assumed to support both functions.
|
||||
#define HAVE_PVALLOC 1
|
||||
#define HAVE_VALLOC 1
|
||||
|
||||
// Assume all others will die on these calls.
|
||||
#define VERIFY_FREE_SIZED_DEATH 1
|
||||
#define VERIFY_FREE_ALIGNED_SIZED_DEATH 1
|
||||
#endif
|
||||
|
||||
// GWP-Asan doesn't do these checks.
|
||||
#if defined(GWP_ASAN_HOOKS)
|
||||
#undef VERIFY_FREE_SIZED_DEATH
|
||||
#undef VERIFY_FREE_ALIGNED_SIZED_DEATH
|
||||
#define VERIFY_FREE_SIZED_DEATH 0
|
||||
#define VERIFY_FREE_ALIGNED_SIZED_DEATH 0
|
||||
#endif
|
||||
|
||||
extern "C" {
|
||||
@ -43,6 +62,8 @@ void malloc_disable(void);
|
||||
int malloc_iterate(uintptr_t base, size_t size,
|
||||
void (*callback)(uintptr_t base, size_t size, void *arg),
|
||||
void *arg);
|
||||
void free_sized(void *ptr, size_t size);
|
||||
void free_aligned_sized(void *ptr, size_t alignment, size_t size);
|
||||
void *valloc(size_t size);
|
||||
void *pvalloc(size_t size);
|
||||
|
||||
@ -424,6 +445,179 @@ TEST_F(ScudoWrappersCTest, Reallocarray) {
|
||||
EXPECT_EQ(errno, ENOMEM);
|
||||
}
|
||||
|
||||
TEST_F(ScudoWrappersCTest, MallocFreeSized) {
|
||||
void *P = malloc(Size);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U);
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_SIZED_DEATH) {
|
||||
EXPECT_DEATH(free_sized(P, Size - 1), "");
|
||||
EXPECT_DEATH(free_sized(P, Size + 1), "");
|
||||
|
||||
// An update to this warning in Clang now triggers in this line, but it's ok
|
||||
// because the check is expecting a bad pointer and should fail.
|
||||
#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
|
||||
#endif
|
||||
EXPECT_DEATH(free_sized(reinterpret_cast<void *>(
|
||||
reinterpret_cast<uintptr_t>(P) | 1U),
|
||||
Size),
|
||||
"");
|
||||
#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
free_sized(P, Size);
|
||||
verifyDeallocHookPtr(P);
|
||||
|
||||
if (VERIFY_FREE_SIZED_DEATH)
|
||||
EXPECT_DEATH(free_sized(P, Size), "");
|
||||
}
|
||||
|
||||
TEST_F(ScudoWrappersCTest, AlignedAllocFreeAlignedSized) {
|
||||
const size_t Alignment = 4096U;
|
||||
const size_t Size = Alignment * 4U;
|
||||
void *P = aligned_alloc(Alignment, Size);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_ALIGNED_SIZED_DEATH) {
|
||||
EXPECT_DEATH(free_aligned_sized(P, Alignment, Size - 1), "");
|
||||
EXPECT_DEATH(free_aligned_sized(P, Alignment, Size + 1), "");
|
||||
EXPECT_DEATH(
|
||||
free_aligned_sized(P, size_t{1} << (sizeof(size_t) * 8 - 1), Size), "");
|
||||
|
||||
// An update to this warning in Clang now triggers in this line, but it's ok
|
||||
// because the check is expecting a bad pointer and should fail.
|
||||
#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wfree-nonheap-object"
|
||||
#endif
|
||||
EXPECT_DEATH(free_aligned_sized(reinterpret_cast<void *>(
|
||||
reinterpret_cast<uintptr_t>(P) | 1U),
|
||||
Alignment, Size),
|
||||
"");
|
||||
#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object")
|
||||
#pragma GCC diagnostic pop
|
||||
#endif
|
||||
}
|
||||
|
||||
free_aligned_sized(P, Alignment, Size);
|
||||
verifyDeallocHookPtr(P);
|
||||
EXPECT_DEATH(free_aligned_sized(P, Alignment, Size), "");
|
||||
}
|
||||
|
||||
TEST_F(ScudoWrappersCDeathTest, MallocFreeAlignedSized) {
|
||||
void *P = malloc(Size);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % FIRST_32_SECOND_64(8U, 16U), 0U);
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_ALIGNED_SIZED_DEATH) {
|
||||
EXPECT_DEATH(free_aligned_sized(P, 8, Size), "");
|
||||
EXPECT_DEATH(free_aligned_sized(P, alignof(std::max_align_t), Size), "");
|
||||
}
|
||||
|
||||
free_sized(P, Size);
|
||||
verifyDeallocHookPtr(P);
|
||||
}
|
||||
|
||||
TEST_F(ScudoWrappersCDeathTest, AlignedAllocFreeSized) {
|
||||
const size_t Alignment = 4096U;
|
||||
const size_t Size = Alignment * 4U;
|
||||
void *P = aligned_alloc(Alignment, Size);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_SIZED_DEATH)
|
||||
EXPECT_DEATH(free_sized(P, Size), "");
|
||||
|
||||
free_aligned_sized(P, Alignment, Size);
|
||||
verifyDeallocHookPtr(P);
|
||||
}
|
||||
|
||||
TEST_F(ScudoWrappersCDeathTest, PosixMemalignFreeSized) {
|
||||
const size_t Alignment = 4096U;
|
||||
const size_t Size = Alignment * 4U;
|
||||
void *P;
|
||||
EXPECT_EQ(posix_memalign(&P, Alignment, Size), 0);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_SIZED_DEATH)
|
||||
EXPECT_DEATH(free_sized(P, Size), "");
|
||||
|
||||
free(P);
|
||||
verifyDeallocHookPtr(P);
|
||||
}
|
||||
|
||||
TEST_F(ScudoWrappersCDeathTest, MemalignFreeSized) {
|
||||
const size_t Alignment = 4096U;
|
||||
const size_t Size = Alignment * 4U;
|
||||
void *P = memalign(Alignment, Size);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
EXPECT_EQ(reinterpret_cast<uintptr_t>(P) % Alignment, 0U);
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_SIZED_DEATH)
|
||||
EXPECT_DEATH(free_sized(P, Size), "");
|
||||
|
||||
free(P);
|
||||
verifyDeallocHookPtr(P);
|
||||
}
|
||||
|
||||
#if HAVE_PVALLOC
|
||||
TEST_F(ScudoWrappersCDeathTest, PvallocFreeSized) {
|
||||
const size_t Size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
|
||||
void *P = pvalloc(Size);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_SIZED_DEATH)
|
||||
EXPECT_DEATH(free_sized(P, Size), "");
|
||||
|
||||
free(P);
|
||||
verifyDeallocHookPtr(P);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if HAVE_VALLOC
|
||||
TEST_F(ScudoWrappersCDeathTest, VallocFreeSized) {
|
||||
const size_t Size = static_cast<size_t>(sysconf(_SC_PAGESIZE));
|
||||
void *P = valloc(Size);
|
||||
EXPECT_NE(P, nullptr);
|
||||
EXPECT_LE(Size, malloc_usable_size(P));
|
||||
verifyAllocHookPtr(P);
|
||||
verifyAllocHookSize(Size);
|
||||
|
||||
if (VERIFY_FREE_SIZED_DEATH)
|
||||
EXPECT_DEATH(free_sized(P, Size), "");
|
||||
|
||||
free(P);
|
||||
verifyDeallocHookPtr(P);
|
||||
}
|
||||
#endif
|
||||
|
||||
#if !SCUDO_FUCHSIA
|
||||
TEST_F(ScudoWrappersCTest, MallOpt) {
|
||||
errno = 0;
|
||||
|
||||
@ -68,6 +68,18 @@ INTERFACE WEAK void SCUDO_PREFIX(free)(void *ptr) {
|
||||
SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Malloc);
|
||||
}
|
||||
|
||||
INTERFACE WEAK void SCUDO_PREFIX(free_sized)(void *ptr, size_t size) {
|
||||
reportDeallocation(ptr);
|
||||
SCUDO_ALLOCATOR.deallocateSized(ptr, scudo::Chunk::Origin::Malloc, size);
|
||||
}
|
||||
|
||||
INTERFACE WEAK void
|
||||
SCUDO_PREFIX(free_aligned_sized)(void *ptr, size_t alignment, size_t size) {
|
||||
reportDeallocation(ptr);
|
||||
SCUDO_ALLOCATOR.deallocateSizedAligned(ptr, scudo::Chunk::Origin::Malloc,
|
||||
size, alignment);
|
||||
}
|
||||
|
||||
INTERFACE WEAK struct SCUDO_MALLINFO SCUDO_PREFIX(mallinfo)(void) {
|
||||
struct SCUDO_MALLINFO Info = {};
|
||||
scudo::StatCounters Stats;
|
||||
@ -324,7 +336,7 @@ INTERFACE WEAK void *SCUDO_PREFIX(aligned_alloc)(size_t alignment,
|
||||
}
|
||||
|
||||
void *Ptr =
|
||||
SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Malloc, alignment);
|
||||
SCUDO_ALLOCATOR.allocate(size, scudo::Chunk::Origin::Memalign, alignment);
|
||||
reportAllocation(Ptr, size);
|
||||
|
||||
return scudo::setErrnoOnNull(Ptr);
|
||||
|
||||
@ -104,47 +104,47 @@ INTERFACE WEAK void operator delete[](void *ptr,
|
||||
}
|
||||
INTERFACE WEAK void operator delete(void *ptr, size_t size) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size);
|
||||
Allocator.deallocateSized(ptr, scudo::Chunk::Origin::New, size);
|
||||
}
|
||||
INTERFACE WEAK void operator delete[](void *ptr, size_t size) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size);
|
||||
Allocator.deallocateSized(ptr, scudo::Chunk::Origin::NewArray, size);
|
||||
}
|
||||
INTERFACE WEAK void operator delete(void *ptr,
|
||||
std::align_val_t align) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0,
|
||||
static_cast<scudo::uptr>(align));
|
||||
Allocator.deallocateAligned(ptr, scudo::Chunk::Origin::New,
|
||||
static_cast<scudo::uptr>(align));
|
||||
}
|
||||
INTERFACE WEAK void operator delete[](void *ptr,
|
||||
std::align_val_t align) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0,
|
||||
static_cast<scudo::uptr>(align));
|
||||
Allocator.deallocateAligned(ptr, scudo::Chunk::Origin::NewArray,
|
||||
static_cast<scudo::uptr>(align));
|
||||
}
|
||||
INTERFACE WEAK void operator delete(void *ptr, std::align_val_t align,
|
||||
std::nothrow_t const &) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0,
|
||||
static_cast<scudo::uptr>(align));
|
||||
Allocator.deallocateAligned(ptr, scudo::Chunk::Origin::New,
|
||||
static_cast<scudo::uptr>(align));
|
||||
}
|
||||
INTERFACE WEAK void operator delete[](void *ptr, std::align_val_t align,
|
||||
std::nothrow_t const &) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0,
|
||||
static_cast<scudo::uptr>(align));
|
||||
Allocator.deallocateAligned(ptr, scudo::Chunk::Origin::NewArray,
|
||||
static_cast<scudo::uptr>(align));
|
||||
}
|
||||
INTERFACE WEAK void operator delete(void *ptr, size_t size,
|
||||
std::align_val_t align) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size,
|
||||
static_cast<scudo::uptr>(align));
|
||||
Allocator.deallocateSizedAligned(ptr, scudo::Chunk::Origin::New, size,
|
||||
static_cast<scudo::uptr>(align));
|
||||
}
|
||||
INTERFACE WEAK void operator delete[](void *ptr, size_t size,
|
||||
std::align_val_t align) NOEXCEPT {
|
||||
reportDeallocation(ptr);
|
||||
Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size,
|
||||
static_cast<scudo::uptr>(align));
|
||||
Allocator.deallocateSizedAligned(ptr, scudo::Chunk::Origin::NewArray, size,
|
||||
static_cast<scudo::uptr>(align));
|
||||
}
|
||||
|
||||
#endif // !SCUDO_ANDROID || !_BIONIC
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user