Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions compiler-rt/lib/scudo/standalone/combined.h
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,8 @@ class Allocator {
Chunk::UnpackedHeader Header = {};
Header.ClassId = QuarantineClassId & Chunk::ClassIdMask;
Header.SizeOrUnusedBytes = sizeof(QuarantineBatch);
Header.State = Chunk::State::Allocated;
// Do not use Allocated or this block will show up in iterateOverChunks.
Copy link
Contributor

@ChiaHungDuan ChiaHungDuan Sep 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given that this is used for Quarantine batch and it does get quarantined as well. We may not need to mention iterateOverChunk which seems to be a workaround. Leaving it without comment or say it's also quarantined seems to be good enough to me.

BTW, maybe we want to consider to rename this allocate deallocate to something more specific to batch class like pushBatchClassBlocks in primary64.h. Just a minor suggestion, feel free to skip it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I just removed the comment.

As to the other thing, yeah we should rename this but I think it's low on the list of priorities so I'm going to leave it alone for now.

Header.State = Chunk::State::Quarantined;
Chunk::storeHeader(Allocator.Cookie, Ptr, &Header);

// Reset tag to 0 as this chunk may have been previously used for a tagged
Expand All @@ -120,7 +121,7 @@ class Allocator {
Chunk::UnpackedHeader Header;
Chunk::loadHeader(Allocator.Cookie, Ptr, &Header);

if (UNLIKELY(Header.State != Chunk::State::Allocated))
if (UNLIKELY(Header.State != Chunk::State::Quarantined))
reportInvalidChunkState(AllocatorAction::Deallocating, Ptr);
DCHECK_EQ(Header.ClassId, QuarantineClassId);
DCHECK_EQ(Header.Offset, 0);
Expand Down
85 changes: 53 additions & 32 deletions compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <set>
#include <stdlib.h>
#include <thread>
#include <unordered_map>
#include <vector>

static constexpr scudo::Chunk::Origin Origin = scudo::Chunk::Origin::Malloc;
Expand Down Expand Up @@ -150,14 +151,8 @@ void TestAllocator<Config>::operator delete(void *ptr) {
}

template <class TypeParam> struct ScudoCombinedTest : public Test {
ScudoCombinedTest() {
UseQuarantine = std::is_same<TypeParam, scudo::AndroidConfig>::value;
Allocator = std::make_unique<AllocatorT>();
}
~ScudoCombinedTest() {
Allocator->releaseToOS(scudo::ReleaseToOS::Force);
UseQuarantine = true;
}
ScudoCombinedTest() { Allocator = std::make_unique<AllocatorT>(); }
~ScudoCombinedTest() { Allocator->releaseToOS(scudo::ReleaseToOS::Force); }

void RunTest();

Expand Down Expand Up @@ -525,30 +520,25 @@ SCUDO_TYPED_TEST(ScudoCombinedTest, IterateOverChunks) {
auto *Allocator = this->Allocator.get();
// Allocates a bunch of chunks, then iterate over all the chunks, ensuring
// they are the ones we allocated. This requires the allocator to not have any
// other allocated chunk at this point (eg: won't work with the Quarantine).
// FIXME: Make it work with UseQuarantine and tagging enabled. Internals of
// iterateOverChunks reads header by tagged and non-tagger pointers so one of
// them will fail.
if (!UseQuarantine) {
std::vector<void *> V;
for (scudo::uptr I = 0; I < 64U; I++)
V.push_back(Allocator->allocate(
static_cast<scudo::uptr>(std::rand()) %
(TypeParam::Primary::SizeClassMap::MaxSize / 2U),
Origin));
Allocator->disable();
Allocator->iterateOverChunks(
0U, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1),
[](uintptr_t Base, UNUSED size_t Size, void *Arg) {
std::vector<void *> *V = reinterpret_cast<std::vector<void *> *>(Arg);
void *P = reinterpret_cast<void *>(Base);
EXPECT_NE(std::find(V->begin(), V->end(), P), V->end());
},
reinterpret_cast<void *>(&V));
Allocator->enable();
for (auto P : V)
Allocator->deallocate(P, Origin);
}
// other allocated chunk at this point.
std::vector<void *> V;
for (scudo::uptr I = 0; I < 64U; I++)
V.push_back(Allocator->allocate(
static_cast<scudo::uptr>(std::rand()) %
(TypeParam::Primary::SizeClassMap::MaxSize / 2U),
Origin));
Allocator->disable();
Allocator->iterateOverChunks(
0U, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1),
[](uintptr_t Base, UNUSED size_t Size, void *Arg) {
std::vector<void *> *V = reinterpret_cast<std::vector<void *> *>(Arg);
void *P = reinterpret_cast<void *>(Base);
EXPECT_NE(std::find(V->begin(), V->end(), P), V->end());
},
reinterpret_cast<void *>(&V));
Allocator->enable();
for (auto P : V)
Allocator->deallocate(P, Origin);
}

SCUDO_TYPED_TEST(ScudoCombinedDeathTest, UseAfterFree) {
Expand Down Expand Up @@ -1161,3 +1151,34 @@ TEST(ScudoCombinedTest, QuarantineDisabled) {
// No quarantine stats should not be present.
EXPECT_EQ(Stats.find("Stats: Quarantine"), std::string::npos);
}

// Verify that no special quarantine blocks appear in iterateOverChunks.
TEST(ScudoCombinedTest, QuarantineIterateOverChunks) {
using AllocatorT = scudo::Allocator<TestQuarantineConfig>;
auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());

// Do a bunch of allocations and deallocations. At the end there should
// be no special quarantine blocks in our callbacks, and no blocks at all.
std::vector<scudo::uptr> Sizes = {128, 128, 256, 256};
for (auto const Size : Sizes) {
void *Ptr = Allocator->allocate(Size, Origin);
EXPECT_NE(Ptr, nullptr);
Allocator->deallocate(Ptr, Origin);
}
std::unordered_map<uintptr_t, size_t> Pointers;
Allocator->disable();
Allocator->iterateOverChunks(
0, static_cast<scudo::uptr>(SCUDO_MMAP_RANGE_SIZE - 1),
[](uintptr_t Base, size_t Size, void *Arg) {
std::unordered_map<uintptr_t, size_t> *Pointers =
reinterpret_cast<std::unordered_map<uintptr_t, size_t> *>(Arg);
(*Pointers)[Base] = Size;
},
reinterpret_cast<void *>(&Pointers));
Allocator->enable();

for (const auto [Base, Size] : Pointers) {
EXPECT_TRUE(false) << "Unexpected pointer found in iterateOverChunks "
<< std::hex << Base << " Size " << std::dec << Size;
}
}