Skip to content

Conversation

@cferris1000
Copy link
Contributor

If a caller has locked memory, then the madvise call will fail. In that case, zero the memory so that we don't return non-zeroed memory for calloc calls since we thought the memory had been released.

If a caller has locked memory, then the madvise call will fail. In
that case, zero the memory so that we don't return non-zeroed memory
for calloc calls since we thought the memory had been released.
@llvmbot
Copy link
Member

llvmbot commented Nov 12, 2025

@llvm/pr-subscribers-compiler-rt-sanitizer

Author: Christopher Ferris (cferris1000)

Changes

If a caller has locked memory, then the madvise call will fail. In that case, zero the memory so that we don't return non-zeroed memory for calloc calls since we thought the memory had been released.


Full diff: https://github.com/llvm/llvm-project/pull/167788.diff

2 Files Affected:

  • (modified) compiler-rt/lib/scudo/standalone/mem_map_linux.cpp (+6-1)
  • (modified) compiler-rt/lib/scudo/standalone/tests/map_test.cpp (+41)
diff --git a/compiler-rt/lib/scudo/standalone/mem_map_linux.cpp b/compiler-rt/lib/scudo/standalone/mem_map_linux.cpp
index 783c4f0d9ab0f..df3e54cab6695 100644
--- a/compiler-rt/lib/scudo/standalone/mem_map_linux.cpp
+++ b/compiler-rt/lib/scudo/standalone/mem_map_linux.cpp
@@ -122,7 +122,12 @@ void MemMapLinux::setMemoryPermissionImpl(uptr Addr, uptr Size, uptr Flags) {
 void MemMapLinux::releaseAndZeroPagesToOSImpl(uptr From, uptr Size) {
   void *Addr = reinterpret_cast<void *>(From);
 
-  while (madvise(Addr, Size, MADV_DONTNEED) == -1 && errno == EAGAIN) {
+  int rc;
+  while ((rc = madvise(Addr, Size, MADV_DONTNEED)) == -1 && errno == EAGAIN) {
+  }
+  if (rc == -1) {
+    // If we can't madvies the memory, then we still need to zero it.
+    memset(Addr, 0, Size);
   }
 }
 
diff --git a/compiler-rt/lib/scudo/standalone/tests/map_test.cpp b/compiler-rt/lib/scudo/standalone/tests/map_test.cpp
index cc7d3ee4dc6c2..fadc0e00c7844 100644
--- a/compiler-rt/lib/scudo/standalone/tests/map_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/map_test.cpp
@@ -14,6 +14,10 @@
 #include <string.h>
 #include <unistd.h>
 
+#if SCUDO_LINUX
+#include <sys/mman.h>
+#endif
+
 static const char *MappingName = "scudo:test";
 
 TEST(ScudoMapTest, PageSize) {
@@ -89,3 +93,40 @@ TEST(ScudoMapTest, MapGrowUnmap) {
   memset(reinterpret_cast<void *>(Q), 0xbb, PageSize);
   MemMap.unmap();
 }
+
+// Verify that zeroing works properly.
+TEST(ScudoMapTest, Zeroing) {
+  scudo::ReservedMemoryT ReservedMemory;
+  const scudo::uptr PageSize = scudo::getPageSizeCached();
+  const scudo::uptr Size = 3 * PageSize;
+  ReservedMemory.create(/*Addr=*/0U, Size, MappingName);
+  ASSERT_TRUE(ReservedMemory.isCreated());
+
+  scudo::MemMapT MemMap = ReservedMemory.dispatch(ReservedMemory.getBase(), ReservedMemory.getCapacity());
+  EXPECT_TRUE(MemMap.remap(MemMap.getBase(), MemMap.getCapacity(), MappingName));
+  unsigned char *Data = reinterpret_cast<unsigned char*>(MemMap.getBase());
+  memset(Data, 1U, MemMap.getCapacity());
+  // Spot check some values.
+  EXPECT_EQ(1U, Data[0]);
+  EXPECT_EQ(1U, Data[PageSize]);
+  EXPECT_EQ(1U, Data[PageSize * 2]);
+  MemMap.releaseAndZeroPagesToOS(MemMap.getBase(), MemMap.getCapacity());
+  EXPECT_EQ(0U, Data[0]);
+  EXPECT_EQ(0U, Data[PageSize]);
+  EXPECT_EQ(0U, Data[PageSize * 2]);
+
+#if SCUDO_LINUX
+  // Now verify that if madvise fails, the data is still zeroed.
+  memset(Data, 1U, MemMap.getCapacity());
+  EXPECT_NE(-1, mlock(Data, MemMap.getCapacity()));
+  EXPECT_EQ(1U, Data[0]);
+  EXPECT_EQ(1U, Data[PageSize]);
+  EXPECT_EQ(1U, Data[PageSize * 2]);
+  MemMap.releaseAndZeroPagesToOS(MemMap.getBase(), MemMap.getCapacity());
+  EXPECT_EQ(0U, Data[0]);
+  EXPECT_EQ(0U, Data[PageSize]);
+  EXPECT_EQ(0U, Data[PageSize * 2]);
+#endif
+
+  MemMap.unmap();
+}

@github-actions
Copy link

github-actions bot commented Nov 12, 2025

✅ With the latest revision this PR passed the C/C++ code formatter.

Comment on lines +128 to +130
if (rc == -1) {
// If we can't madvies the memory, then we still need to zero it.
memset(Addr, 0, Size);
Copy link
Contributor

Choose a reason for hiding this comment

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

I was thinking if we want to check something like "errno == EPERM". It seems not making any difference to the problem but maybe it helps with some other cases. I guess not...

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yeah, when I checked this the errno was EINVAL, which is not what I was expecting. It seems safer to just memset when it fails for whatever reason.

@cferris1000 cferris1000 merged commit 3e28992 into llvm:main Nov 13, 2025
10 checks passed
@cferris1000 cferris1000 deleted the zero_fix branch November 13, 2025 21:01
@mgorny
Copy link
Member

mgorny commented Nov 15, 2025

The new test is failing for me while testing on Gentoo/arm64:

FAIL: ScudoStandalone-Unit :: ./ScudoUnitTest-aarch64-Test/74/171 (4339 of 5799)
******************** TEST 'ScudoStandalone-Unit :: ./ScudoUnitTest-aarch64-Test/74/171' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt_build/lib/scudo/standalone/tests/./ScudoUnitTest-aarch64-Test-ScudoStandalone-Unit-190-74-171.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=171 GTEST_SHARD_INDEX=74 /var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt_build/lib/scudo/standalone/tests/./ScudoUnitTest-aarch64-Test
--

Script:
--
/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt_build/lib/scudo/standalone/tests/./ScudoUnitTest-aarch64-Test --gtest_filter=ScudoMapTest.Zeroing
--
/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:123: Failure
Expected: (-1) != (mlock(Data, MemMap.getCapacity())), actual: -1 vs -1

/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:133: Failure
Expected: (-1) != (munlock(Data, MemMap.getCapacity())), actual: -1 vs -1


/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:123
Expected: (-1) != (mlock(Data, MemMap.getCapacity())), actual: -1 vs -1

/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:133
Expected: (-1) != (munlock(Data, MemMap.getCapacity())), actual: -1 vs -1



********************
FAIL: ScudoStandalone-Unit-GwpAsanTorture :: ./ScudoUnitTest-aarch64-Test/74/171 (4530 of 5799)
******************** TEST 'ScudoStandalone-Unit-GwpAsanTorture :: ./ScudoUnitTest-aarch64-Test/74/171' FAILED ********************
Script(shard):
--
GTEST_OUTPUT=json:/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt_build/lib/scudo/standalone/tests/./ScudoUnitTest-aarch64-Test-ScudoStandalone-Unit-GwpAsanTorture-190-74-171.json GTEST_SHUFFLE=0 GTEST_TOTAL_SHARDS=171 GTEST_SHARD_INDEX=74 /var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt_build/lib/scudo/standalone/tests/./ScudoUnitTest-aarch64-Test
--

Script:
--
/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt_build/lib/scudo/standalone/tests/./ScudoUnitTest-aarch64-Test --gtest_filter=ScudoMapTest.Zeroing
--
/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:123: Failure
Expected: (-1) != (mlock(Data, MemMap.getCapacity())), actual: -1 vs -1

/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:133: Failure
Expected: (-1) != (munlock(Data, MemMap.getCapacity())), actual: -1 vs -1


/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:123
Expected: (-1) != (mlock(Data, MemMap.getCapacity())), actual: -1 vs -1

/var/tmp/portage/llvm-runtimes/compiler-rt-sanitizers-22.0.0.9999/work/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:133
Expected: (-1) != (munlock(Data, MemMap.getCapacity())), actual: -1 vs -1



********************

This is on top of d9dfe75. Neoverse-N1. glibc 2.41.

@madanial0
Copy link
Contributor

The new test also fails on ppc64le starting with https://lab.llvm.org/buildbot/#/builders/145/builds/10835

/home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-rhel-test/clang-ppc64le-rhel/build/runtimes/runtimes-bins/compiler-rt/lib/scudo/standalone/tests/./ScudoUnitTest-powerpc64le-Test --gtest_filter=ScudoMapTest.Zeroing
--
/home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-rhel-test/clang-ppc64le-rhel/llvm-project/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:123: Failure
Expected: (-1) != (mlock(Data, MemMap.getCapacity())), actual: -1 vs -1
/home/buildbots/llvm-external-buildbots/workers/ppc64le-clang-rhel-test/clang-ppc64le-rhel/llvm-project/compiler-rt/lib/scudo/standalone/tests/map_test.cpp:123
Expected: (-1) != (mlock(Data, MemMap.getCapacity())), actual: -1 vs -1

@ZarkoT
Copy link
Contributor

ZarkoT commented Nov 17, 2025

@cferris1000 Do you mind having a look at the ppc64le failures, possibly reverting until you can investigate further?
https://lab.llvm.org/buildbot/#/builders/95/builds/19135

@cferris1000
Copy link
Contributor Author

This looks like mlock/munlock are not supported everywhere, so I'll fix the test right now.

@cferris1000
Copy link
Contributor Author

I believe this should pull request should fix this:

#168448

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

6 participants