-
Notifications
You must be signed in to change notification settings - Fork 15.2k
[scudo] Skip special quarantine blocks in iterateOverChunks #159892
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
If a quarantine block is allocated, it will be passed through to a user who calls iterateOverChunks. Make these special block allocations state Quarantined so they are not passed to iterateOverChunks. Remove the FIXME in the combined tests for quarantine since this fixes those tests too. Also add a specific new test that fails without this fix.
@llvm/pr-subscribers-compiler-rt-sanitizer Author: Christopher Ferris (cferris1000) ChangesIf a quarantine block is allocated, it will be passed through to a user who calls iterateOverChunks. Make these special block allocations state Quarantined so they are not passed to iterateOverChunks. Remove the FIXME in the combined tests for quarantine since this fixes those tests too. Also add a specific new test that fails without this fix. Full diff: https://github.com/llvm/llvm-project/pull/159892.diff 2 Files Affected:
diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h
index c9ba28a52f780..1ef6c5cd5698a 100644
--- a/compiler-rt/lib/scudo/standalone/combined.h
+++ b/compiler-rt/lib/scudo/standalone/combined.h
@@ -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.
+ 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
@@ -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);
diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
index 1eff9ebcb7a4f..5de0846624507 100644
--- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
@@ -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;
@@ -151,12 +152,10 @@ 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;
}
void RunTest();
@@ -525,30 +524,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) {
@@ -1161,3 +1155,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;
+ }
+}
|
I verified this on a device with MTE on and MTE off and all tests pass. |
✅ With the latest revision this PR passed the C/C++ code formatter. |
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. |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
If a quarantine block is allocated, it will be passed through to a user who calls iterateOverChunks. Make these special block allocations state Quarantined so they are not passed to iterateOverChunks.
Remove the FIXME in the combined tests for quarantine since this fixes those tests too.
Also add a specific new test that fails without this fix.