Skip to content

[scudo] Create a non-static getErrorInfo function.#199770

Open
cferris1000 wants to merge 1 commit into
llvm:mainfrom
cferris1000:get_error
Open

[scudo] Create a non-static getErrorInfo function.#199770
cferris1000 wants to merge 1 commit into
llvm:mainfrom
cferris1000:get_error

Conversation

@cferris1000
Copy link
Copy Markdown
Contributor

Create a getErrorInfo function that operates on the Allocator and doesn't require passing in all of the extra information.

Add interface function __scudo_get_fault_error_info that calls this new function.

Add all needed functions to support the new getErrorInfo.

This is being added to replace the static version from Android that required linking in a copy of libscudo to use. This new version will be used directly from libc.

Create a getErrorInfo function that operates on the Allocator and
doesn't require passing in all of the extra information.

Add interface function __scudo_get_fault_error_info that calls this
new function.

Add all needed functions to support the new getErrorInfo.

This is being added to replace the static version from Android
that required linking in a copy of libscudo to use. This new version
will be used directly from libc.
@llvmorg-github-actions
Copy link
Copy Markdown

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

Author: Christopher Ferris (cferris1000)

Changes

Create a getErrorInfo function that operates on the Allocator and doesn't require passing in all of the extra information.

Add interface function __scudo_get_fault_error_info that calls this new function.

Add all needed functions to support the new getErrorInfo.

This is being added to replace the static version from Android that required linking in a copy of libscudo to use. This new version will be used directly from libc.


Patch is 21.21 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/199770.diff

9 Files Affected:

  • (modified) compiler-rt/lib/scudo/standalone/combined.h (+180)
  • (modified) compiler-rt/lib/scudo/standalone/include/scudo/interface.h (+3)
  • (modified) compiler-rt/lib/scudo/standalone/memtag.h (+10)
  • (modified) compiler-rt/lib/scudo/standalone/primary32.h (+3)
  • (modified) compiler-rt/lib/scudo/standalone/primary64.h (+53)
  • (modified) compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt (+1)
  • (added) compiler-rt/lib/scudo/standalone/tests/error_info_test.cpp (+160)
  • (modified) compiler-rt/lib/scudo/standalone/tests/primary_test.cpp (+31)
  • (modified) compiler-rt/lib/scudo/standalone/wrappers_c.cpp (+11)
diff --git a/compiler-rt/lib/scudo/standalone/combined.h b/compiler-rt/lib/scudo/standalone/combined.h
index 72b4b361475b2..b37ff23343946 100644
--- a/compiler-rt/lib/scudo/standalone/combined.h
+++ b/compiler-rt/lib/scudo/standalone/combined.h
@@ -988,6 +988,186 @@ class Allocator {
                : 0;
   }
 
+  void getErrorInfo(uptr FaultAddr, size_t MinDistance, size_t MaxDistance,
+                    scudo_error_report *Reports, size_t &ReportIndex) {
+    auto *RingBuffer = getRingBuffer();
+    if (RingBuffer == nullptr)
+      return;
+
+    // No more room for any more error reports.
+    if (ReportIndex == NumErrorReports)
+      return;
+
+    uptr UntaggedFaultAddr = untagPointer(FaultAddr);
+    BlockInfo Info = Primary.findNearestBlock(UntaggedFaultAddr);
+
+    auto GetBlockInfo = [&](uptr Addr, Chunk::UnpackedHeader *Header,
+                            uptr &ChunkAddr) {
+      uptr ChunkOffset = getChunkOffsetFromBlock(
+          reinterpret_cast<const char *>(loadTagUnaligned(Addr)));
+      ChunkAddr = loadTagUnaligned(Addr + ChunkOffset);
+      uptr HeaderAddr = loadTag(Addr + ChunkOffset - Chunk::getHeaderSize());
+      *Header = *reinterpret_cast<const Chunk::UnpackedHeader *>(HeaderAddr);
+    };
+
+    auto CheckOOB = [&](uptr BlockAddr) {
+      if (BlockAddr < Info.RegionBegin || BlockAddr >= Info.RegionEnd)
+        return false;
+
+      uptr ChunkAddr;
+      Chunk::UnpackedHeader Header;
+      GetBlockInfo(BlockAddr, &Header, ChunkAddr);
+      if (Header.State != Chunk::State::Allocated)
+        return false;
+      const u8 ChunkTag = extractTag(ChunkAddr);
+      if (extractTag(FaultAddr) != ChunkTag) {
+        // If the allocated size is zero, then there isn't a tag on this
+        // pointer, so allow a tag mismatch.
+        if (ChunkTag != 0 || Header.SizeOrUnusedBytes != 0)
+          return false;
+      }
+      auto *Report = &Reports[ReportIndex++];
+      uptr UntaggedChunkAddr = untagPointer(ChunkAddr);
+      const u32 *ChunkData = reinterpret_cast<const u32 *>(UntaggedChunkAddr);
+      Report->error_type = UntaggedFaultAddr < UntaggedChunkAddr
+                               ? BUFFER_UNDERFLOW
+                               : BUFFER_OVERFLOW;
+      Report->allocation_address = UntaggedChunkAddr;
+      Report->allocation_size = Header.SizeOrUnusedBytes;
+      if (RingBuffer->Depot) {
+        const u32 *TracePtr = reinterpret_cast<const u32 *>(loadTagUnaligned(
+            reinterpret_cast<uptr>(&ChunkData[MemTagAllocationTraceIndex])));
+        collectTraceMaybe(RingBuffer->Depot, Report->allocation_trace,
+                          *TracePtr);
+      }
+      const u32 *TidPtr = reinterpret_cast<const u32 *>(loadTagUnaligned(
+          reinterpret_cast<uptr>(&ChunkData[MemTagAllocationTidIndex])));
+      Report->allocation_tid = *TidPtr;
+      return ReportIndex == NumErrorReports;
+    };
+
+    if (MinDistance == 0 && CheckOOB(Info.BlockBegin))
+      return;
+
+    for (size_t I = Max<size_t>(MinDistance, 1); I != MaxDistance; ++I)
+      if (CheckOOB(Info.BlockBegin + I * Info.BlockSize) ||
+          CheckOOB(Info.BlockBegin - I * Info.BlockSize))
+        return;
+  }
+
+  void getRingBufferErrorInfo(uintptr_t FaultAddr, scudo_error_report *Reports,
+                              size_t &ReportIndex) {
+    auto *RingBuffer = getRingBuffer();
+    if (RingBuffer == nullptr)
+      return;
+
+    // No more room for any more error reports.
+    if (ReportIndex == NumErrorReports)
+      return;
+
+    uptr Pos = atomic_load_relaxed(&RingBuffer->Pos);
+    if (Pos == 0)
+      return;
+
+    const uptr RingBufferElements = RingBuffer->RingBufferElements;
+
+    // Pos is a value that is always increasing.
+    uptr LastPos;
+    if (Pos < RingBufferElements) {
+      LastPos = 0;
+    } else {
+      LastPos = Pos - RingBufferElements;
+    }
+    for (uptr I = Pos; I > LastPos; --I) {
+      auto *Entry =
+          getRingBufferEntry(RingBuffer, (I - 1) % RingBufferElements);
+      uptr EntryPtr = atomic_load_relaxed(&Entry->Ptr);
+      if (!EntryPtr)
+        continue;
+
+      uptr UntaggedEntryPtr = untagPointer(EntryPtr);
+      uptr EntrySize = atomic_load_relaxed(&Entry->AllocationSize);
+      u32 AllocationTrace = atomic_load_relaxed(&Entry->AllocationTrace);
+      u32 AllocationTid = atomic_load_relaxed(&Entry->AllocationTid);
+      u32 DeallocationTrace = atomic_load_relaxed(&Entry->DeallocationTrace);
+      u32 DeallocationTid = atomic_load_relaxed(&Entry->DeallocationTid);
+      if (DeallocationTid) {
+        // For UAF we only consider in-bounds fault addresses because
+        // out-of-bounds UAF is rare and attempting to detect it is very likely
+        // to result in false positives.
+        if (FaultAddr < EntryPtr || FaultAddr >= EntryPtr + EntrySize)
+          continue;
+      } else {
+        // Ring buffer OOB is only possible with secondary allocations. In this
+        // case we are guaranteed a guard region of at least a page on either
+        // side of the allocation (guard page on the right, guard page + tagged
+        // region on the left), so ignore any faults outside of that range.
+        if (FaultAddr < EntryPtr - getPageSizeCached() ||
+            FaultAddr >= EntryPtr + EntrySize + getPageSizeCached())
+          continue;
+
+        // For UAF the ring buffer will contain two entries, one for the
+        // allocation and another for the deallocation. Don't report buffer
+        // overflow/underflow using the allocation entry if we have already
+        // collected a report from the deallocation entry.
+        bool Found = false;
+        for (uptr J = 0; J != ReportIndex; ++J) {
+          if (Reports[J].allocation_address == UntaggedEntryPtr) {
+            Found = true;
+            break;
+          }
+        }
+        if (Found)
+          continue;
+      }
+
+      auto *Report = &Reports[ReportIndex++];
+      if (DeallocationTid)
+        Report->error_type = USE_AFTER_FREE;
+      else if (FaultAddr < EntryPtr)
+        Report->error_type = BUFFER_UNDERFLOW;
+      else
+        Report->error_type = BUFFER_OVERFLOW;
+
+      Report->allocation_address = UntaggedEntryPtr;
+      Report->allocation_size = EntrySize;
+      collectTraceMaybe(RingBuffer->Depot, Report->allocation_trace,
+                        AllocationTrace);
+      Report->allocation_tid = AllocationTid;
+      collectTraceMaybe(RingBuffer->Depot, Report->deallocation_trace,
+                        DeallocationTrace);
+      Report->deallocation_tid = DeallocationTid;
+      if (ReportIndex == NumErrorReports) {
+        // No more report entries.
+        return;
+      }
+    }
+  }
+
+  void getErrorInfo(uintptr_t FaultAddr, struct scudo_error_info *ErrorInfo) {
+    const Options Options = Primary.Options.load();
+    if (!useMemoryTagging<AllocatorConfig>(Options))
+      return;
+
+    bool TagExists = extractTag(FaultAddr) != 0;
+    size_t ReportIndex = 0;
+    if (TagExists) {
+      // Check for OOB in the current block and the two surrounding blocks.
+      // Beyond that, UAF is more likely.
+      getErrorInfo(FaultAddr, 0, 2, &ErrorInfo->reports[0], ReportIndex);
+    }
+
+    // Check the ring buffer. For primary allocations this will only find UAF;
+    // for secondary allocations we can find either UAF or OOB.
+    getRingBufferErrorInfo(FaultAddr, &ErrorInfo->reports[0], ReportIndex);
+
+    if (TagExists) {
+      // Check for OOB in the 28 blocks surrounding the 3 we checked earlier.
+      // Beyond that we are likely to hit false positives.
+      getErrorInfo(FaultAddr, 2, 16, &ErrorInfo->reports[0], ReportIndex);
+    }
+  }
+
   static const uptr MaxTraceSize = 64;
 
   static void collectTraceMaybe(const StackDepot *Depot,
diff --git a/compiler-rt/lib/scudo/standalone/include/scudo/interface.h b/compiler-rt/lib/scudo/standalone/include/scudo/interface.h
index 9f2b93891999d..cf804594c677f 100644
--- a/compiler-rt/lib/scudo/standalone/include/scudo/interface.h
+++ b/compiler-rt/lib/scudo/standalone/include/scudo/interface.h
@@ -123,6 +123,9 @@ size_t __scudo_get_region_info_size(void);
 const char *__scudo_get_ring_buffer_addr(void);
 size_t __scudo_get_ring_buffer_size(void);
 
+void __scudo_get_fault_error_info(uintptr_t fault_addr,
+                                  struct scudo_error_info *error_info);
+
 #ifndef M_DECAY_TIME
 #define M_DECAY_TIME -100
 #endif
diff --git a/compiler-rt/lib/scudo/standalone/memtag.h b/compiler-rt/lib/scudo/standalone/memtag.h
index 073e72c46f68a..653745f3d6147 100644
--- a/compiler-rt/lib/scudo/standalone/memtag.h
+++ b/compiler-rt/lib/scudo/standalone/memtag.h
@@ -259,6 +259,11 @@ inline uptr loadTag(uptr Ptr) {
   return TaggedPtr;
 }
 
+inline uptr loadTagUnaligned(uptr Ptr) {
+  uptr AlignedPtr = Ptr & ~static_cast<uptr>(0xF);
+  return loadTag(AlignedPtr) | (Ptr & 0xF);
+}
+
 #else
 
 inline constexpr bool systemSupportsMemoryTagging() { return false; }
@@ -303,6 +308,11 @@ inline NORETURN uptr loadTag(uptr Ptr) {
   UNREACHABLE("memory tagging not supported");
 }
 
+inline uptr loadTagUnaligned(uptr Ptr) {
+  (void)Ptr;
+  UNREACHABLE("memory tagging not supported");
+}
+
 #endif
 
 #pragma GCC diagnostic push
diff --git a/compiler-rt/lib/scudo/standalone/primary32.h b/compiler-rt/lib/scudo/standalone/primary32.h
index 5720c9f308dac..c3705a2b474eb 100644
--- a/compiler-rt/lib/scudo/standalone/primary32.h
+++ b/compiler-rt/lib/scudo/standalone/primary32.h
@@ -134,6 +134,9 @@ template <typename Config> class SizeClassAllocator32 {
   const char *getRegionInfoArrayAddress() const { return nullptr; }
   static uptr getRegionInfoArraySize() { return 0; }
 
+  // Not supported in SizeClassAllocator32.
+  BlockInfo findNearestBlock(UNUSED uptr Ptr) { return {}; }
+
   // Not supported in SizeClassAllocator32.
   static BlockInfo findNearestBlock(UNUSED const char *RegionInfoData,
                                     UNUSED uptr Ptr) {
diff --git a/compiler-rt/lib/scudo/standalone/primary64.h b/compiler-rt/lib/scudo/standalone/primary64.h
index 6ee56d6c99291..2c3ccdf028530 100644
--- a/compiler-rt/lib/scudo/standalone/primary64.h
+++ b/compiler-rt/lib/scudo/standalone/primary64.h
@@ -93,6 +93,8 @@ template <typename Config> class SizeClassAllocator64 {
   static BlockInfo findNearestBlock(const char *RegionInfoData,
                                     uptr Ptr) NO_THREAD_SAFETY_ANALYSIS;
 
+  BlockInfo findNearestBlock(uptr Ptr);
+
   void init(s32 ReleaseToOsInterval) NO_THREAD_SAFETY_ANALYSIS;
 
   void unmapTestOnly();
@@ -1350,6 +1352,57 @@ uptr SizeClassAllocator64<Config>::releaseToOS(ReleaseToOS ReleaseType) {
   return TotalReleasedBytes;
 }
 
+template <typename Config>
+BlockInfo SizeClassAllocator64<Config>::findNearestBlock(uptr Ptr)
+    NO_THREAD_SAFETY_ANALYSIS {
+  uptr ClassId;
+  uptr MinDistance = -1UL;
+  for (uptr I = 0; I != NumClasses; ++I) {
+    if (I == SizeClassMap::BatchClassId)
+      continue;
+
+    ScopedLock ML(RegionInfoArray[I].MMLock);
+    uptr Begin = RegionInfoArray[I].RegionBeg;
+    uptr End = Begin + RegionInfoArray[I].MemMapInfo.AllocatedUser;
+    if (Begin > End || End - Begin < SizeClassMap::getSizeByClassId(I))
+      continue;
+    uptr RegionDistance;
+    if (Begin <= Ptr) {
+      if (Ptr < End)
+        RegionDistance = 0;
+      else
+        RegionDistance = Ptr - End;
+    } else {
+      RegionDistance = Begin - Ptr;
+    }
+
+    if (RegionDistance < MinDistance) {
+      MinDistance = RegionDistance;
+      ClassId = I;
+      if (RegionDistance == 0)
+        break;
+    }
+  }
+
+  if (MinDistance > 8192) {
+    return {};
+  }
+
+  ScopedLock ML(RegionInfoArray[ClassId].MMLock);
+  BlockInfo B = {};
+  B.RegionBegin = RegionInfoArray[ClassId].RegionBeg;
+  B.RegionEnd =
+      B.RegionBegin + RegionInfoArray[ClassId].MemMapInfo.AllocatedUser;
+  B.BlockSize = SizeClassMap::getSizeByClassId(ClassId);
+  B.BlockBegin = B.RegionBegin + uptr(sptr(Ptr - B.RegionBegin) /
+                                      sptr(B.BlockSize) * sptr(B.BlockSize));
+  while (B.BlockBegin < B.RegionBegin)
+    B.BlockBegin += B.BlockSize;
+  while (B.RegionEnd < B.BlockBegin + B.BlockSize)
+    B.BlockBegin -= B.BlockSize;
+  return B;
+}
+
 template <typename Config>
 /* static */ BlockInfo SizeClassAllocator64<Config>::findNearestBlock(
     const char *RegionInfoData, uptr Ptr) NO_THREAD_SAFETY_ANALYSIS {
diff --git a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt
index a85eb737dba0a..68ffc16bce780 100644
--- a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt
+++ b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt
@@ -93,6 +93,7 @@ set(SCUDO_UNIT_TEST_SOURCES
   combined_test.cpp
   common_test.cpp
   condition_variable_test.cpp
+  error_info_test.cpp
   flags_test.cpp
   list_test.cpp
   map_test.cpp
diff --git a/compiler-rt/lib/scudo/standalone/tests/error_info_test.cpp b/compiler-rt/lib/scudo/standalone/tests/error_info_test.cpp
new file mode 100644
index 0000000000000..d8d4dc4c027bc
--- /dev/null
+++ b/compiler-rt/lib/scudo/standalone/tests/error_info_test.cpp
@@ -0,0 +1,160 @@
+//===-- error_info_test.cpp -------------------------------------*- C++ -*-===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+#include "allocator_config.h"
+#include "combined.h"
+#include "memtag.h"
+#include "tests/scudo_unit_test.h"
+#include <stdlib.h>
+
+namespace scudo {
+
+template <typename Config> struct TestAllocator : Allocator<Config> {
+  TestAllocator() { this->initThreadMaybe(); }
+  ~TestAllocator() { this->unmapTestOnly(); }
+};
+
+template <class TypeParam> struct ScudoErrorInfoTest : public Test {
+  ScudoErrorInfoTest() {
+    setenv("SCUDO_OPTIONS", "allocation_ring_buffer_size=32768", 1);
+    Allocator = std::make_unique<AllocatorT>();
+    Allocator->setTrackAllocationStacks(true);
+  }
+
+  ~ScudoErrorInfoTest() {
+    Allocator->releaseToOS(scudo::ReleaseToOS::Force);
+    unsetenv("SCUDO_OPTIONS");
+  }
+
+  using AllocatorT = TestAllocator<TypeParam>;
+  std::unique_ptr<AllocatorT> Allocator;
+};
+
+struct TestConfig : public scudo::DefaultConfig {
+  static const bool MaySupportMemoryTagging = true;
+};
+
+using ScudoErrorInfoTestTypes = ::testing::Types<TestConfig>;
+TYPED_TEST_SUITE(ScudoErrorInfoTest, ScudoErrorInfoTestTypes);
+
+TYPED_TEST(ScudoErrorInfoTest, RingBufferErrorInfo) {
+  auto *Allocator = this->Allocator.get();
+  if (!scudo::archSupportsMemoryTagging() ||
+      !Allocator->useMemoryTaggingTestOnly()) {
+    GTEST_SKIP() << "MTE not supported or enabled";
+  }
+
+  EXPECT_GT(Allocator->getRingBufferSize(), 0u);
+  EXPECT_NE(nullptr, Allocator->getRingBufferAddress());
+
+  const scudo::uptr Size = 64U;
+  void *P = nullptr;
+  P = Allocator->allocate(Size, Chunk::Origin::Malloc);
+  ASSERT_NE(P, nullptr);
+  Allocator->deallocate(P, Chunk::Origin::Malloc);
+
+  scudo::uptr Ptr = reinterpret_cast<scudo::uptr>(P);
+
+  scudo_error_info ErrorInfo = {};
+  size_t ReportIndex = 0;
+  Allocator->getRingBufferErrorInfo(Ptr, &ErrorInfo.reports[0], ReportIndex);
+
+  EXPECT_EQ(ReportIndex, 1U);
+  EXPECT_EQ(ErrorInfo.reports[0].error_type, USE_AFTER_FREE);
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_address, scudo::untagPointer(Ptr));
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_size, Size);
+
+  // Now verify the ReportIndex is followed.
+  memset(&ErrorInfo, 0, sizeof(ErrorInfo));
+  Allocator->getRingBufferErrorInfo(Ptr, &ErrorInfo.reports[0], ReportIndex);
+  EXPECT_EQ(ReportIndex, 2U);
+  EXPECT_EQ(ErrorInfo.reports[0].error_type, UNKNOWN);
+  EXPECT_EQ(ErrorInfo.reports[1].error_type, USE_AFTER_FREE);
+  EXPECT_EQ(ErrorInfo.reports[1].allocation_address, scudo::untagPointer(Ptr));
+  EXPECT_EQ(ErrorInfo.reports[1].allocation_size, Size);
+
+  // Verify if at max, nothing happens.
+  ReportIndex = 3;
+  memset(&ErrorInfo, 0, sizeof(ErrorInfo));
+  Allocator->getRingBufferErrorInfo(Ptr, &ErrorInfo.reports[0], ReportIndex);
+  EXPECT_EQ(ReportIndex, 3U);
+  EXPECT_EQ(ErrorInfo.reports[0].error_type, UNKNOWN);
+  EXPECT_EQ(ErrorInfo.reports[1].error_type, UNKNOWN);
+  EXPECT_EQ(ErrorInfo.reports[2].error_type, UNKNOWN);
+}
+
+TYPED_TEST(ScudoErrorInfoTest, GetErrorInfoUAF) {
+  auto *Allocator = this->Allocator.get();
+  if (!scudo::archSupportsMemoryTagging() ||
+      !Allocator->useMemoryTaggingTestOnly()) {
+    GTEST_SKIP() << "MTE not supported or enabled";
+  }
+
+  const scudo::uptr Size = 64U;
+  void *P = Allocator->allocate(Size, Chunk::Origin::Malloc);
+  ASSERT_NE(P, nullptr);
+  Allocator->deallocate(P, Chunk::Origin::Malloc);
+
+  scudo::uptr Ptr = reinterpret_cast<scudo::uptr>(P);
+  scudo_error_info ErrorInfo = {};
+  Allocator->getErrorInfo(Ptr, &ErrorInfo);
+
+  EXPECT_EQ(ErrorInfo.reports[0].error_type, USE_AFTER_FREE);
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_address, scudo::untagPointer(Ptr));
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_size, Size);
+}
+
+TYPED_TEST(ScudoErrorInfoTest, GetErrorInfoOverflow) {
+  auto *Allocator = this->Allocator.get();
+  if (!scudo::archSupportsMemoryTagging() ||
+      !Allocator->useMemoryTaggingTestOnly()) {
+    GTEST_SKIP() << "MTE not supported or enabled";
+  }
+
+  const scudo::uptr Size = 64U;
+  void *P = Allocator->allocate(Size, Chunk::Origin::Malloc);
+  ASSERT_NE(P, nullptr);
+
+  scudo::uptr PtrAddr = reinterpret_cast<scudo::uptr>(P);
+  scudo::uptr FaultAddr = PtrAddr + Size;
+  scudo_error_info ErrorInfo = {};
+  Allocator->getErrorInfo(FaultAddr, &ErrorInfo);
+
+  EXPECT_EQ(ErrorInfo.reports[0].error_type, BUFFER_OVERFLOW);
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_address,
+            scudo::untagPointer(PtrAddr));
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_size, Size);
+
+  Allocator->deallocate(P, Chunk::Origin::Malloc);
+}
+
+TYPED_TEST(ScudoErrorInfoTest, GetErrorInfoUnderflow) {
+  auto *Allocator = this->Allocator.get();
+  if (!scudo::archSupportsMemoryTagging() ||
+      !Allocator->useMemoryTaggingTestOnly()) {
+    GTEST_SKIP() << "MTE not supported or enabled";
+  }
+
+  const scudo::uptr Size = 64U;
+  void *P = Allocator->allocate(Size, Chunk::Origin::Malloc);
+  ASSERT_NE(P, nullptr);
+
+  scudo::uptr PtrAddr = reinterpret_cast<scudo::uptr>(P);
+  scudo::uptr FaultAddr = PtrAddr - 1;
+  scudo_error_info ErrorInfo = {};
+  Allocator->getErrorInfo(FaultAddr, &ErrorInfo);
+
+  EXPECT_EQ(ErrorInfo.reports[0].error_type, BUFFER_UNDERFLOW);
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_address,
+            scudo::untagPointer(PtrAddr));
+  EXPECT_EQ(ErrorInfo.reports[0].allocation_size, Size);
+
+  Allocator->deallocate(P, Chunk::Origin::Malloc);
+}
+
+} // namespace scudo
diff --git a/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp b/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp
index 3a087c497b1a9..2fbfadfbf6240 100644
--- a/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp
+++ b/compiler-rt/lib/scudo/standalone/tests/primary_test.cpp
@@ -237,6 +237,37 @@ SCUDO_TYPED_TEST(ScudoPrimaryTest, BasicPrimary) {
   }
 }
 
+SCUDO_TYPED_TEST(ScudoPrimaryTest, FindNearestBlock) {
+  using Primary = TestAllocator<TypeParam, scudo::DefaultSizeClassMap>;
+  std::unique_ptr<Primary> Allocator(new Primary);
+  Allocator->init(/*ReleaseToOsInterval=*/-1);
+  typename Primary::SizeClassAllocatorT SizeClassAllocator;
+  SizeClassAllocator.init(nullptr, Allocator.get());
+
+  const scudo::uptr Size = 64U;
+  if (Primary::canAllocate(Size)) {
+    const scudo::uptr ClassId = Primary::SizeClassMap::getClassIdBySize(Size);
+    void *P = SizeClassAllocator.allocate(ClassId);
+    ASSERT_NE(P, nullptr);
+
+    scudo::uptr Ptr = reinterpret_cast<scudo::uptr>(P);
+    scudo::BlockInfo Info = Allocator->findNearestBlock(Ptr);
+
+    if (Info.BlockSize != 0) {
+      EXPECT_EQ(Info.BlockBegin, Ptr);
+      EXPECT_EQ(Info.BlockSize,
+                Primary::SizeClassMap::getSizeByClassId(ClassId));
+
+      scudo::BlockInfo Info2 = Allocator->findNearestBlock(Ptr + 10);
+      EXPECT_EQ(Info2.BlockBegin, Ptr);
+    }
+
+    SizeClassAllocator.deallocate(ClassId, P);
+  }
+
+  SizeClassAllocator.destroy(nullptr);
+}
+
 struct SmallRegionsConfig {
 ...
[truncated]

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.

1 participant