[compiler-rt] [scudo] Add primary option to zero block on dealloc. (PR #142394)
via llvm-commits
llvm-commits at lists.llvm.org
Fri Dec 12 07:56:46 PST 2025
https://github.com/piwicode updated https://github.com/llvm/llvm-project/pull/142394
>From 91ca3b8e23f6b678bb16f26e388fbbb852d5390a Mon Sep 17 00:00:00 2001
From: Pierre Labatut <plabatut at google.com>
Date: Mon, 2 Jun 2025 15:19:02 +0200
Subject: [PATCH] [scudo] Add an option to zero block on dealloc.
When all the blocks of a page are unused, the page will be full of zero
and decommitted on operating systems that scan the memory.
Change-Id: I278055d82057090b0a04d812b49cf93fdf467478
---
.../lib/scudo/standalone/allocator_config.def | 7 ++
compiler-rt/lib/scudo/standalone/combined.h | 19 +++++
compiler-rt/lib/scudo/standalone/flags.inc | 6 ++
.../scudo/standalone/tests/combined_test.cpp | 77 +++++++++++++++++++
4 files changed, 109 insertions(+)
diff --git a/compiler-rt/lib/scudo/standalone/allocator_config.def b/compiler-rt/lib/scudo/standalone/allocator_config.def
index 0aea7b8f2fb9a..935f3b953e45e 100644
--- a/compiler-rt/lib/scudo/standalone/allocator_config.def
+++ b/compiler-rt/lib/scudo/standalone/allocator_config.def
@@ -61,6 +61,13 @@ BASE_OPTIONAL(const bool, QuarantineDisabled, false)
// If set to false, return the total available size in the allocation.
BASE_OPTIONAL(const bool, ExactUsableSize, true)
+#ifdef SCUDO_FUCHSIA
+// Writes zeros to the memory slot when a allocation is returned to
+// the allocator. This decommits memory on operating systems that
+// detects pages full of zero."
+BASE_OPTIONAL(const bool, EnableZeroOnDealloc, false)
+#endif
+
// PRIMARY_REQUIRED_TYPE(NAME)
//
// SizeClassMap to use with the Primary.
diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h
index ffe9554203241..f93e112281133 100644
--- a/compiler-rt/lib/scudo/standalone/combined.h
+++ b/compiler-rt/lib/scudo/standalone/combined.h
@@ -177,6 +177,10 @@ class Allocator {
QuarantineMaxChunkSize =
static_cast<u32>(getFlags()->quarantine_max_chunk_size);
+#ifdef SCUDO_FUCHSIA
+ ZeroOnDeallocMaxSize =
+ static_cast<u32>(getFlags()->zero_on_dealloc_max_size);
+#endif
Stats.init();
// TODO(chiahungduan): Given that we support setting the default value in
@@ -1035,6 +1039,9 @@ class Allocator {
u32 Cookie = 0;
u32 QuarantineMaxChunkSize = 0;
+#ifdef SCUDO_FUCHSIA
+ u32 ZeroOnDeallocMaxSize = 0;
+#endif
GlobalStats Stats;
PrimaryT Primary;
@@ -1345,6 +1352,18 @@ class Allocator {
BlockBegin = retagBlock(Options, TaggedPtr, Ptr, Header, Size, true);
}
+#ifdef SCUDO_FUCHSIA
+ if (AllocatorConfig::getEnableZeroOnDealloc()) {
+ // Clearing the header and is incompatible with quarantine and tagging.
+ // Hence, it is fine to implement it only when quanrantine is bypassed.
+ DCHECK(!useMemoryTagging<AllocatorConfig>(Options));
+ uptr length = reinterpret_cast<uptr>(Ptr) + Size -
+ reinterpret_cast<uptr>(BlockBegin);
+ if (length <= ZeroOnDeallocMaxSize)
+ memset(BlockBegin, 0, length);
+ }
+#endif // SCUDO_FUCHSIA
+
const uptr ClassId = Header->ClassId;
if (LIKELY(ClassId)) {
bool CacheDrained;
diff --git a/compiler-rt/lib/scudo/standalone/flags.inc b/compiler-rt/lib/scudo/standalone/flags.inc
index ff0c28e1db7c4..b1f7ae68d8798 100644
--- a/compiler-rt/lib/scudo/standalone/flags.inc
+++ b/compiler-rt/lib/scudo/standalone/flags.inc
@@ -32,6 +32,12 @@ 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[]).")
+#ifdef SCUDO_FUCHSIA
+SCUDO_FLAG(int, zero_on_dealloc_max_size, 0,
+ "Only chunks smaller or equal to this threshold will be zeroed on "
+ "deallocation. Requires zero on dealloc to be enabled.")
+#endif
+
SCUDO_FLAG(bool, zero_contents, false, "Zero chunk contents on allocation.")
SCUDO_FLAG(bool, pattern_fill_contents, false,
diff --git a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
index 4837ac96b9b26..83e9e3360841c 100644
--- a/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/combined_test.cpp
@@ -19,6 +19,7 @@
#include <algorithm>
#include <condition_variable>
+#include <cstdio>
#include <memory>
#include <mutex>
#include <set>
@@ -1077,6 +1078,82 @@ struct TestQuarantineSizeClassConfig {
static const scudo::uptr SizeDelta = 0;
};
+#ifdef SCUDO_FUCHSIA
+struct TestZeroOnDeallocConfig {
+ static const bool MaySupportMemoryTagging = false;
+ static const bool EnableZeroOnDealloc = true;
+ static const bool QuarantineDisabled = true;
+
+ template <class A> using TSDRegistryT = scudo::TSDRegistrySharedT<A, 1U, 1U>;
+ struct Primary {
+ // Tiny allocator, its Primary only serves chunks of four sizes.
+ using SizeClassMap =
+ scudo::FixedSizeClassMap<TestQuarantineSizeClassConfig>;
+ static const scudo::uptr RegionSizeLog = DeathRegionSizeLog;
+ static const scudo::s32 MinReleaseToOsIntervalMs = INT32_MIN;
+ static const scudo::s32 MaxReleaseToOsIntervalMs = INT32_MAX;
+ typedef scudo::uptr CompactPtrT;
+ static const scudo::uptr CompactPtrScale = 0;
+ static const bool EnableRandomOffset = true;
+ static const scudo::uptr MapSizeIncrement = 1UL << 18;
+ static const scudo::uptr GroupSizeLog = 18;
+ };
+
+ template <typename Config>
+ using PrimaryT = scudo::SizeClassAllocator64<Config>;
+
+ struct Secondary {
+ template <typename Config>
+ using CacheT = scudo::MapAllocatorNoCache<Config>;
+ };
+
+ template <typename Config> using SecondaryT = scudo::MapAllocator<Config>;
+};
+
+struct TestNoZeroOnDeallocConfig : public TestZeroOnDeallocConfig {
+ static const bool EnableZeroOnDealloc = false;
+};
+
+TEST(ScudoCombinedTest, ZeroOnDeallocEnabledAndFlag) {
+ ([]() { // Cleanup on exit scope.
+ for (scudo::uptr FlagValue = 128; FlagValue <= 2048; FlagValue *= 2) {
+ // Set the size limit flag via the environment variable.
+ char Options[256];
+ snprintf(Options, sizeof(Options),
+ "SCUDO_OPTIONS=zero_on_dealloc_max_size=%ld",
+ static_cast<long>(FlagValue));
+ putenv(Options);
+
+ // Creates an allocator, configured from the environment.
+ using AllocatorT = TestAllocator<TestZeroOnDeallocConfig>;
+ auto Allocator = std::unique_ptr<AllocatorT>(new AllocatorT());
+
+ for (scudo::uptr AllocatedSize : {FlagValue / 2, FlagValue}) {
+ // Allocates and sets the memory.
+ void *P = Allocator->allocate(AllocatedSize, Origin);
+ ASSERT_NE(P, nullptr);
+ memset(P, 'B', AllocatedSize);
+
+ char *Begin =
+ reinterpret_cast<char *>(Allocator->getBlockBeginTestOnly(P));
+ char *End = reinterpret_cast<char *>(P) + AllocatedSize;
+ // Deallocates and eventually clears the memory.
+ Allocator->deallocate(P, Origin);
+
+ if (End - Begin <= static_cast<long>(FlagValue)) {
+ // Verifies the memory was cleared, including the header.
+ for (char *T = Begin; T < End; ++T) {
+ ASSERT_EQ(*T, 0);
+ }
+ }
+ }
+ }
+ })();
+ // Clear the scudo flag option.
+ unsetenv("SCUDO_OPTIONS");
+}
+#endif
+
struct TestQuarantineConfig {
static const bool MaySupportMemoryTagging = false;
More information about the llvm-commits
mailing list