diff --git a/compiler-rt/lib/scudo/standalone/chunk.h b/compiler-rt/lib/scudo/standalone/chunk.h index 9da2dc57e71a1..96f6f3c70cec0 100644 --- a/compiler-rt/lib/scudo/standalone/chunk.h +++ b/compiler-rt/lib/scudo/standalone/chunk.h @@ -53,10 +53,11 @@ 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, free, free_sized + New = 1, // operator new, operator delete + NewArray = 2, // operator new [], operator delete [] + Memalign = 3, // aligned_alloc, posix_memalign, memalign, pvalloc, valloc, + // free_aligned_sized }; enum State : u8 { Available = 0, Allocated = 1, Quarantined = 2 }; diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h index 87acdec2a3bac..09dd9ce3f4b97 100644 --- a/compiler-rt/lib/scudo/standalone/combined.h +++ b/compiler-rt/lib/scudo/standalone/combined.h @@ -170,6 +170,12 @@ class Allocator { Primary.Options.set(OptionBit::DeallocTypeMismatch); if (getFlags()->delete_size_mismatch) Primary.Options.set(OptionBit::DeleteSizeMismatch); + if (getFlags()->free_size_mismatch) + Primary.Options.set(OptionBit::FreeSizeMismatch); + if (getFlags()->free_alignment_mismatch) + Primary.Options.set(OptionBit::FreeAlignmentMismatch); + if (getFlags()->delete_alignment_mismatch) + Primary.Options.set(OptionBit::DeleteAlignmentMismatch); if (allocatorSupportsMemoryTagging() && systemSupportsMemoryTagging()) Primary.Options.set(OptionBit::UseMemoryTagging); @@ -433,7 +439,8 @@ class Allocator { } NOINLINE void deallocate(void *Ptr, Chunk::Origin Origin, uptr DeleteSize = 0, - UNUSED uptr Alignment = MinAlignment) { + bool HasDeleteSize = false, uptr DeleteAlignment = 0, + bool HasDeleteAlignment = false) { if (UNLIKELY(!Ptr)) return; @@ -456,6 +463,9 @@ class Allocator { } #endif // GWP_ASAN_HOOKS + if (UNLIKELY(HasDeleteAlignment && !isPowerOfTwo(DeleteAlignment))) + reportAlignmentNotPowerOfTwo(DeleteAlignment); + if (UNLIKELY(!isAligned(reinterpret_cast(Ptr), MinAlignment))) reportMisalignedPointer(AllocatorAction::Deallocating, Ptr); @@ -470,19 +480,41 @@ class Allocator { 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); - } + if (UNLIKELY(isOriginMismatch( + static_cast(Header.OriginOrWasZeroed), Origin, + HasDeleteSize))) + reportDeallocTypeMismatch(AllocatorAction::Deallocating, Ptr, + Header.OriginOrWasZeroed, Origin, + HasDeleteSize); } const uptr Size = getSize(Ptr, &Header); - if (DeleteSize && Options.get(OptionBit::DeleteSizeMismatch)) { - if (UNLIKELY(DeleteSize != Size)) - reportDeleteSizeMismatch(Ptr, DeleteSize, Size); + switch (Origin) { + case Chunk::Origin::New: + FALLTHROUGH; + case Chunk::Origin::NewArray: + if (Options.get(OptionBit::DeleteSizeMismatch) && HasDeleteSize) { + if (UNLIKELY(DeleteSize != Size)) + reportDeleteSizeMismatch(Ptr, DeleteSize, Size); + } + if (Options.get(OptionBit::DeleteAlignmentMismatch) && + HasDeleteAlignment) { + if (UNLIKELY(!isAligned(reinterpret_cast(Ptr), DeleteAlignment))) + reportDeleteAlignmentMismatch(Ptr, DeleteAlignment); + } + break; + case Chunk::Origin::Memalign: + if (Options.get(OptionBit::FreeAlignmentMismatch) && HasDeleteAlignment) { + if (UNLIKELY(!isAligned(reinterpret_cast(Ptr), DeleteAlignment))) + reportFreeAlignmentMismatch(Ptr, DeleteAlignment); + } + FALLTHROUGH; + case Chunk::Origin::Malloc: + if (Options.get(OptionBit::FreeSizeMismatch) && HasDeleteSize) { + if (UNLIKELY(DeleteSize != Size)) + reportFreeSizeMismatch(Ptr, DeleteSize, Size); + } + break; } quarantineOrDeallocateChunk(Options, TaggedPtr, &Header, Size); @@ -529,14 +561,20 @@ class Allocator { if (UNLIKELY(Header.State != Chunk::State::Allocated)) reportInvalidChunkState(AllocatorAction::Reallocating, OldPtr); - // 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); + // There is no language prohibiting the use of realloc with + // aligned_alloc/posix_memalign/memalign and etc. The outcome in + // practice is that the newly allocated memory will typically not + // have the same alignment but will have minimum alignment. With + // regards to operator new, there is no guarantee that the allocator + // being used with malloc is the same as operator new. There is also + // no guarantee that they share the same minimum alignment guarantees. + // So we reject these. + if (UNLIKELY(Header.OriginOrWasZeroed == Chunk::Origin::New || + Header.OriginOrWasZeroed == Chunk::Origin::NewArray)) + reportDeallocTypeMismatch( + AllocatorAction::Reallocating, OldPtr, Header.OriginOrWasZeroed, + Chunk::Origin::Malloc, /*HasDeleteSize=*/false); } void *BlockBegin = getBlockBegin(OldTaggedPtr, &Header); @@ -1746,6 +1784,19 @@ class Allocator { return (Bytes - sizeof(AllocationRingBuffer)) / sizeof(typename AllocationRingBuffer::Entry); } + + static bool isOriginMismatch(Chunk::Origin Alloc, Chunk::Origin Dealloc, + bool HasDeleteSize) { + if (Alloc == Dealloc) { + return false; + } + if (Alloc == Chunk::Origin::Memalign && Dealloc == Chunk::Origin::Malloc && + !HasDeleteSize) { + // aligned_alloc with free is allowed, but not free_sized. + return false; + } + return true; + } }; } // namespace scudo diff --git a/compiler-rt/lib/scudo/standalone/flags.inc b/compiler-rt/lib/scudo/standalone/flags.inc index ff0c28e1db7c4..cd83d962509b7 100644 --- a/compiler-rt/lib/scudo/standalone/flags.inc +++ b/compiler-rt/lib/scudo/standalone/flags.inc @@ -49,3 +49,18 @@ SCUDO_FLAG(int, release_to_os_interval_ms, 5000, SCUDO_FLAG(int, allocation_ring_buffer_size, 32768, "Entries to keep in the allocation ring buffer for scudo. " "Values less or equal to zero disable the buffer.") + +SCUDO_FLAG(bool, delete_alignment_mismatch, true, + "Terminate on an alignment mismatch between a aligned-delete and " + "the actual " + "alignment of a chunk (as provided to new/new[]).") + +SCUDO_FLAG(bool, free_size_mismatch, true, + "Terminate on a size mismatch between a free_sized and the actual " + "size of a chunk (as provided to malloc/calloc/realloc).") + +SCUDO_FLAG(bool, free_alignment_mismatch, true, + "Terminate on an alignment mismatch between a free_aligned_sized " + "and the actual " + "alignment of a chunk (as provided to " + "aligned_alloc/posix_memalign/memalign).") diff --git a/compiler-rt/lib/scudo/standalone/internal_defs.h b/compiler-rt/lib/scudo/standalone/internal_defs.h index 27c6b451ffe72..36e393213a004 100644 --- a/compiler-rt/lib/scudo/standalone/internal_defs.h +++ b/compiler-rt/lib/scudo/standalone/internal_defs.h @@ -47,6 +47,15 @@ #define UNUSED __attribute__((unused)) #define USED __attribute__((used)) #define NOEXCEPT noexcept +#ifdef __has_attribute +#if __has_attribute(fallthrough) +#define FALLTHROUGH __attribute__((fallthrough)) +#else +#define FALLTHROUGH +#endif +#else +#define FALLTHROUGH +#endif // This check is only available on Clang. This is essentially an alias of // C++20's 'constinit' specifier which will take care of this when (if?) we can diff --git a/compiler-rt/lib/scudo/standalone/options.h b/compiler-rt/lib/scudo/standalone/options.h index b20142a415903..d77b9111890cc 100644 --- a/compiler-rt/lib/scudo/standalone/options.h +++ b/compiler-rt/lib/scudo/standalone/options.h @@ -25,6 +25,9 @@ enum class OptionBit { UseOddEvenTags, UseMemoryTagging, AddLargeAllocationSlack, + DeleteAlignmentMismatch, + FreeSizeMismatch, + FreeAlignmentMismatch, }; struct Options { diff --git a/compiler-rt/lib/scudo/standalone/report.cpp b/compiler-rt/lib/scudo/standalone/report.cpp index b97a74b078c2f..4fdbfe238ae49 100644 --- a/compiler-rt/lib/scudo/standalone/report.cpp +++ b/compiler-rt/lib/scudo/standalone/report.cpp @@ -142,13 +142,48 @@ void NORETURN reportMisalignedPointer(AllocatorAction Action, const void *Ptr) { stringifyAction(Action), Ptr); } +static const char *stringifyAllocOrigin(Chunk::Origin AllocOrigin) { + switch (AllocOrigin) { + case Chunk::Origin::Malloc: + return "malloc"; + case Chunk::Origin::New: + return "operator new"; + case Chunk::Origin::NewArray: + return "operator new []"; + case Chunk::Origin::Memalign: + return "aligned_alloc"; + } + return ""; +} + +static const char *stringifyDeallocOrigin(Chunk::Origin DeallocOrigin, + bool HasDeleteSize) { + switch (DeallocOrigin) { + case Chunk::Origin::Malloc: + if (HasDeleteSize) + return "free_sized"; + return "free"; + case Chunk::Origin::New: + return "operator delete"; + case Chunk::Origin::NewArray: + return "operator delete []"; + case Chunk::Origin::Memalign: + return "free_aligned_sized"; + } + return ""; +} + // 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 TypeA, u8 TypeB, + bool HasDeleteSize) { 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 vs %s)\n", + stringifyAction(Action), Ptr, + stringifyAllocOrigin(static_cast(TypeA)), + stringifyDeallocOrigin(static_cast(TypeB), HasDeleteSize)); } // 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, Size, ExpectedSize); } +void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment) { + ScopedErrorReport Report; + Report.append( + "invalid aligned delete when deallocating address %p (%zu vs %zu)\n", Ptr, + getLeastSignificantSetBitIndex(reinterpret_cast(Ptr)), Alignment); +} + +void NORETURN reportFreeSizeMismatch(const void *Ptr, uptr Size, + uptr ExpectedSize) { + ScopedErrorReport Report; + Report.append( + "invalid sized free when deallocating address %p (%zu vs %zu)\n", Ptr, + Size, ExpectedSize); +} + +void NORETURN reportFreeAlignmentMismatch(const void *Ptr, uptr Alignment) { + ScopedErrorReport Report; + Report.append( + "invalid aligned free when deallocating address %p (%zu vs %zu)\n", Ptr, + getLeastSignificantSetBitIndex(reinterpret_cast(Ptr)), Alignment); +} + void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment) { ScopedErrorReport Report; Report.append( diff --git a/compiler-rt/lib/scudo/standalone/report.h b/compiler-rt/lib/scudo/standalone/report.h index c397dd3fc9c65..085446ae27081 100644 --- a/compiler-rt/lib/scudo/standalone/report.h +++ b/compiler-rt/lib/scudo/standalone/report.h @@ -44,9 +44,13 @@ 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 TypeA, u8 TypeB, bool HasDeleteSize); void NORETURN reportDeleteSizeMismatch(const void *Ptr, uptr Size, uptr ExpectedSize); +void NORETURN reportDeleteAlignmentMismatch(const void *Ptr, uptr Alignment); +void NORETURN reportFreeSizeMismatch(const void *Ptr, uptr Size, + uptr ExpectedSize); +void NORETURN reportFreeAlignmentMismatch(const void *Ptr, uptr Alignment); // C wrappers errors. void NORETURN reportAlignmentNotPowerOfTwo(uptr Alignment); diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp index 7e8d5b4396d2e..6026c3b2e7f9d 100644 --- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp @@ -327,7 +327,7 @@ void ScudoCombinedTest::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->deallocate(P, Origin, Size, true); } } @@ -374,7 +374,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroContents) { for (scudo::uptr I = 0; I < Size; I++) ASSERT_EQ((reinterpret_cast(P))[I], '\0'); memset(P, 0xaa, Size); - Allocator->deallocate(P, Origin, Size); + Allocator->deallocate(P, Origin, Size, true); } } } @@ -392,7 +392,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ZeroFill) { for (scudo::uptr I = 0; I < Size; I++) ASSERT_EQ((reinterpret_cast(P))[I], '\0'); memset(P, 0xaa, Size); - Allocator->deallocate(P, Origin, Size); + Allocator->deallocate(P, Origin, Size, true); } } } @@ -709,7 +709,7 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, ThreadedCombined) { while (!V.empty()) { auto Pair = V.back(); - Allocator->deallocate(Pair.first, Origin, Pair.second); + Allocator->deallocate(Pair.first, Origin, Pair.second, true); V.pop_back(); } }); @@ -782,26 +782,26 @@ TEST(ScudoCombinedDeathTest, DeathCombined) { EXPECT_NE(P, nullptr); // Invalid sized deallocation. - EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U), ""); + EXPECT_DEATH(Allocator->deallocate(P, Origin, Size + 8U, true), ""); // Misaligned pointer. Potentially unused if EXPECT_DEATH isn't available. UNUSED void *MisalignedP = reinterpret_cast(reinterpret_cast(P) | 1U); - EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size), ""); - EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U), ""); + EXPECT_DEATH(Allocator->deallocate(MisalignedP, Origin, Size, true), ""); + EXPECT_DEATH(Allocator->reallocate(MisalignedP, Size * 2U, true), ""); // Header corruption. scudo::u64 *H = reinterpret_cast(scudo::Chunk::getAtomicHeader(P)); *H ^= 0x42U; - EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), ""); + EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), ""); *H ^= 0x420042U; - EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), ""); + EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), ""); *H ^= 0x420000U; // Invalid chunk state. - Allocator->deallocate(P, Origin, Size); - EXPECT_DEATH(Allocator->deallocate(P, Origin, Size), ""); + Allocator->deallocate(P, Origin, Size, true); + EXPECT_DEATH(Allocator->deallocate(P, Origin, Size, true), ""); EXPECT_DEATH(Allocator->reallocate(P, Size * 2U), ""); EXPECT_DEATH(Allocator->getUsableSize(P), ""); } @@ -908,13 +908,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->deallocate(Ptrs[I], Origin, Size, true); 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->deallocate(Ptrs[I], Origin, Size - 8, true); 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) diff --git a/compiler-rt/lib/scudo/standalone/tests/report_test.cpp b/compiler-rt/lib/scudo/standalone/tests/report_test.cpp index 514837df1a43a..18c5717c11c2f 100644 --- a/compiler-rt/lib/scudo/standalone/tests/report_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/report_test.cpp @@ -41,7 +41,7 @@ TEST(ScudoReportDeathTest, Generic) { scudo::reportMisalignedPointer(scudo::AllocatorAction::Deallocating, P), "Scudo ERROR.*deallocating.*42424242"); EXPECT_DEATH(scudo::reportDeallocTypeMismatch( - scudo::AllocatorAction::Reallocating, P, 0, 1), + scudo::AllocatorAction::Reallocating, P, 0, 1, false), "Scudo ERROR.*reallocating.*42424242"); EXPECT_DEATH(scudo::reportDeleteSizeMismatch(P, 123, 456), "Scudo ERROR.*42424242.*123.*456"); diff --git a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h index 27c0e591a2099..cf06c221d1a5c 100644 --- a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h +++ b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h @@ -8,6 +8,8 @@ #include "platform.h" +#include + #if SCUDO_FUCHSIA #include using Test = ::zxtest::Test; @@ -58,4 +60,15 @@ using Test = ::testing::Test; #define SCUDO_NO_TEST_MAIN #endif +// 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" +#define SKIP_DEALLOC_TYPE_MISMATCH 1 +#else +#define DEALLOC_TYPE_MISMATCH "true" +#define SKIP_DEALLOC_TYPE_MISMATCH \ + (getenv("SKIP_DEALLOC_TYPE_MISMATCH") != nullptr) +#endif + extern bool UseQuarantine; diff --git a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp index 881e0265bb341..d672abb99db0c 100644 --- a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test_main.cpp @@ -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; diff --git a/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp b/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp index f5e17d7214863..970ccc9c422f6 100644 --- a/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/wrappers_c_test.cpp @@ -45,6 +45,8 @@ int malloc_iterate(uintptr_t base, size_t size, void *arg); void *valloc(size_t size); void *pvalloc(size_t size); +void free_sized(void *ptr, size_t size); +void free_aligned_sized(void *ptr, size_t alignment, size_t size); #ifndef SCUDO_ENABLE_HOOKS_TESTS #define SCUDO_ENABLE_HOOKS_TESTS 0 @@ -186,6 +188,184 @@ TEST_F(ScudoWrappersCDeathTest, Malloc) { 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(P) % FIRST_32_SECOND_64(8U, 16U), 0U); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + if (!SKIP_DEALLOC_TYPE_MISMATCH) { + 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(reinterpret_cast(P) | 1U), + Size), + ""); +#if defined(__has_warning) && __has_warning("-Wfree-nonheap-object") +#pragma GCC diagnostic pop +#endif + + free_sized(P, Size); + verifyDeallocHookPtr(P); + 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(P) % Alignment, 0U); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + if (!SKIP_DEALLOC_TYPE_MISMATCH) { + 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( + reinterpret_cast(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) { + if (SKIP_DEALLOC_TYPE_MISMATCH) + TEST_SKIP("Dealloc type mismatch disabled."); + + void *P = malloc(Size); + EXPECT_NE(P, nullptr); + EXPECT_LE(Size, malloc_usable_size(P)); + EXPECT_EQ(reinterpret_cast(P) % FIRST_32_SECOND_64(8U, 16U), 0U); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + 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) { + if (SKIP_DEALLOC_TYPE_MISMATCH) + TEST_SKIP("Dealloc type mismatch disabled."); + + 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(P) % Alignment, 0U); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + EXPECT_DEATH(free_sized(P, Size), ""); + + free_aligned_sized(P, Alignment, Size); + verifyDeallocHookPtr(P); +} + +TEST_F(ScudoWrappersCDeathTest, PosixMemalignFreeSized) { + if (SKIP_DEALLOC_TYPE_MISMATCH) + TEST_SKIP("Dealloc type mismatch disabled."); + + 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(P) % Alignment, 0U); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + EXPECT_DEATH(free_sized(P, Size), ""); + + free(P); + verifyDeallocHookPtr(P); +} + +TEST_F(ScudoWrappersCDeathTest, MemalignFreeSized) { + if (SKIP_DEALLOC_TYPE_MISMATCH) + TEST_SKIP("Dealloc type mismatch disabled."); + + 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(P) % Alignment, 0U); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + EXPECT_DEATH(free_sized(P, Size), ""); + + free(P); + verifyDeallocHookPtr(P); +} + +TEST_F(ScudoWrappersCDeathTest, PvallocFreeSized) { + if (SKIP_DEALLOC_TYPE_MISMATCH) + TEST_SKIP("Dealloc type mismatch disabled."); + + const size_t Size = static_cast(sysconf(_SC_PAGESIZE)); + void *P = pvalloc(Size); + EXPECT_NE(P, nullptr); + EXPECT_LE(Size, malloc_usable_size(P)); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + EXPECT_DEATH(free_sized(P, Size), ""); + + free(P); + verifyDeallocHookPtr(P); +} + +TEST_F(ScudoWrappersCDeathTest, VallocFreeSized) { + if (SKIP_DEALLOC_TYPE_MISMATCH) + TEST_SKIP("Dealloc type mismatch disabled."); + + const size_t Size = static_cast(sysconf(_SC_PAGESIZE)); + void *P = valloc(Size); + EXPECT_NE(P, nullptr); + EXPECT_LE(Size, malloc_usable_size(P)); + verifyAllocHookPtr(P); + verifyAllocHookSize(Size); + + EXPECT_DEATH(free_sized(P, Size), ""); + + free(P); + verifyDeallocHookPtr(P); +} + TEST_F(ScudoWrappersCTest, Calloc) { void *P = calloc(1U, Size); EXPECT_NE(P, nullptr); diff --git a/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp b/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp index c802ed22fbad0..85f804eecb2db 100644 --- a/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp +++ b/compiler-rt/lib/scudo/standalone/tests/wrappers_cpp_test.cpp @@ -17,13 +17,6 @@ #include #include -// Android does not support checking for new/delete mismatches. -#if SCUDO_ANDROID -#define SKIP_MISMATCH_TESTS 1 -#else -#define SKIP_MISMATCH_TESTS 0 -#endif - void operator delete(void *, size_t) noexcept; void operator delete[](void *, size_t) noexcept; @@ -143,10 +136,9 @@ class Pixel { // by Scudo. See the comment in the C counterpart of this file. TEST_F(ScudoWrappersCppDeathTest, New) { - if (getenv("SKIP_TYPE_MISMATCH") || SKIP_MISMATCH_TESTS) { - printf("Skipped type mismatch tests.\n"); - return; - } + if (SKIP_DEALLOC_TYPE_MISMATCH) + TEST_SKIP("Dealloc type mismatch disabled."); + testCxxNew(); testCxxNew(); testCxxNew(); diff --git a/compiler-rt/lib/scudo/standalone/wrappers_c.inc b/compiler-rt/lib/scudo/standalone/wrappers_c.inc index 59f3fb0962f8b..5acd8a2b0f748 100644 --- a/compiler-rt/lib/scudo/standalone/wrappers_c.inc +++ b/compiler-rt/lib/scudo/standalone/wrappers_c.inc @@ -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.deallocate(ptr, scudo::Chunk::Origin::Malloc, size, true); +} + +INTERFACE WEAK void +SCUDO_PREFIX(free_aligned_sized)(void *ptr, size_t alignment, size_t size) { + reportDeallocation(ptr); + SCUDO_ALLOCATOR.deallocate(ptr, scudo::Chunk::Origin::Memalign, size, true, + alignment, true); +} + INTERFACE WEAK struct SCUDO_MALLINFO SCUDO_PREFIX(mallinfo)(void) { struct SCUDO_MALLINFO Info = {}; scudo::StatCounters Stats; @@ -308,7 +320,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); diff --git a/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp b/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp index 098d4f71acc4a..ac51082d10120 100644 --- a/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp +++ b/compiler-rt/lib/scudo/standalone/wrappers_cpp.cpp @@ -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.deallocate(ptr, scudo::Chunk::Origin::New, size, true); } INTERFACE WEAK void operator delete[](void *ptr, size_t size) NOEXCEPT { reportDeallocation(ptr); - Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size); + Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size, true); } 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(align)); + Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0, false, + static_cast(align), true); } 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(align)); + Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0, false, + static_cast(align), true); } 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(align)); + Allocator.deallocate(ptr, scudo::Chunk::Origin::New, 0, false, + static_cast(align), true); } 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(align)); + Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, 0, false, + static_cast(align), true); } 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(align)); + Allocator.deallocate(ptr, scudo::Chunk::Origin::New, size, true, + static_cast(align), true); } 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(align)); + Allocator.deallocate(ptr, scudo::Chunk::Origin::NewArray, size, true, + static_cast(align), true); } #endif // !SCUDO_ANDROID || !_BIONIC diff --git a/compiler-rt/test/scudo/standalone/unit/gwp_asan/lit.site.cfg.py.in b/compiler-rt/test/scudo/standalone/unit/gwp_asan/lit.site.cfg.py.in index c12b72c009b2e..85fa7eac93de7 100644 --- a/compiler-rt/test/scudo/standalone/unit/gwp_asan/lit.site.cfg.py.in +++ b/compiler-rt/test/scudo/standalone/unit/gwp_asan/lit.site.cfg.py.in @@ -21,4 +21,4 @@ config.test_source_root = config.test_exec_root config.environment['SCUDO_OPTIONS'] = 'GWP_ASAN_SampleRate=1:GWP_ASAN_MaxSimultaneousAllocations=100000' # GWP-ASan doesn't support malloc-type mismatch. -config.environment['SKIP_TYPE_MISMATCH'] = '1' +config.environment['SKIP_DEALLOC_TYPE_MISMATCH'] = '1' diff --git a/llvm/docs/ScudoHardenedAllocator.rst b/llvm/docs/ScudoHardenedAllocator.rst index 328c883bbd5db..cd52b267635af 100644 --- a/llvm/docs/ScudoHardenedAllocator.rst +++ b/llvm/docs/ScudoHardenedAllocator.rst @@ -267,6 +267,16 @@ The following "string" options are available: | | | the scudo_malloc_set_track_allocation_stacks | | | | function. | +---------------------------------+----------------+-------------------------------------------------+ +| delete_alignment_mismatch | true | Whether or not we report errors on mismatch | +| | | between alignment of new and delete. | ++---------------------------------+----------------+-------------------------------------------------+ +| free_size_mismatch | true | Whether or not we report errors on mismatch | +| | | between sizes of malloc and free_sized. | ++---------------------------------+----------------+-------------------------------------------------+ +| free_alignment_mismatch | true | Whether or not we report errors on mismatch | +| | | between alignment of aligned_alloc and | +| | | free_aligned_sized. | ++---------------------------------+----------------+-------------------------------------------------+ Additional flags can be specified, for example if Scudo if compiled with `GWP-ASan `_ support.