[llvm] [ProfileData] Bound binary sample profile primitive reads (PR #215476)

Sergey Shcherbinin via llvm-commits llvm-commits at lists.llvm.org
Thu Aug 13 21:57:00 PDT 2026


https://github.com/SergeyShch01 updated https://github.com/llvm/llvm-project/pull/215476

>From 2f9a8e34ea30ba1591120ce6f7b3eeeba947b361 Mon Sep 17 00:00:00 2001
From: Sergey Shcherbinin <sscherbinin at nvidia.com>
Date: Tue, 4 Aug 2026 14:35:13 +0400
Subject: [PATCH] [ProfileData] Bound binary sample profile primitive reads

Fix pre-existing latent memory-safety issues that could cause out-of-bounds
reads or invalid pointer arithmetic on malformed, truncated, or
non-null-terminated buffers:

* unbounded ULEB128 decoding in readNumber() and Raw/Ext format probes;
* unbounded terminator searches in readString() and GCC format detection;
* unsafe bounds checking in readUnencodedNumber().

Use bounded decoding, memchr, overflow-safe size checks, and typed ULEB128
errors. Preserve the existing decodeULEB128 interface and avoid test-only
ABI exports.

Add functional and guard-page tests that detect boundary regressions
without requiring sanitizers.
---
 llvm/include/llvm/Support/LEB128.h            |  46 ++-
 llvm/lib/ProfileData/SampleProfReader.cpp     |  82 +++--
 llvm/unittests/ProfileData/SampleProfTest.cpp | 326 ++++++++++++++++++
 llvm/unittests/Support/LEB128Test.cpp         |  23 ++
 4 files changed, 441 insertions(+), 36 deletions(-)

diff --git a/llvm/include/llvm/Support/LEB128.h b/llvm/include/llvm/Support/LEB128.h
index bf45131582005..8d5788e6034dd 100644
--- a/llvm/include/llvm/Support/LEB128.h
+++ b/llvm/include/llvm/Support/LEB128.h
@@ -123,13 +123,31 @@ inline unsigned encodeULEB128(uint64_t Value, uint8_t *p,
   return (unsigned)(p - orig_p);
 }
 
-/// Utility function to decode a ULEB128 value.
+/// Identifies why ULEB128 decoding failed.
+enum class ULEB128DecodeError {
+  /// No decoding error has been reported.
+  None,
+  /// The encoding requires bytes beyond the supplied buffer.
+  UnexpectedEnd,
+  /// The encoded value does not fit in uint64_t.
+  TooBig,
+};
+
+/// Utility function to decode a ULEB128 value and report a typed error.
 ///
-/// If \p error is non-null, it will point to a static error message,
-/// if an error occurred. It will not be modified on success.
-inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
-                              const uint8_t *end = nullptr,
-                              const char **error = nullptr) {
+/// \p p is the first byte of the encoding.
+/// If \p n is non-null, it receives the number of bytes consumed.
+/// If \p end is non-null, decoding will not read at or beyond that address.
+/// If \p error is non-null, it will point to a static error message if an error
+/// occurred. It will not be modified on success.
+/// If \p errorCode is non-null, it will identify the decoding outcome. It is
+/// set to \c None on entry and only changed when decoding fails.
+inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n, const uint8_t *end,
+                              const char **error,
+                              ULEB128DecodeError *errorCode) {
+  if (errorCode)
+    *errorCode = ULEB128DecodeError::None;
+
   const uint8_t *orig_p = p;
   uint64_t Value = 0;
   unsigned Shift = 0;
@@ -137,6 +155,8 @@ inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
     if (LLVM_UNLIKELY(p == end)) {
       if (error)
         *error = "malformed uleb128, extends past end";
+      if (errorCode)
+        *errorCode = ULEB128DecodeError::UnexpectedEnd;
       Value = 0;
       break;
     }
@@ -146,6 +166,8 @@ inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
          (Shift > 63 && Slice != 0))) {
       if (error)
         *error = "uleb128 too big for uint64";
+      if (errorCode)
+        *errorCode = ULEB128DecodeError::TooBig;
       Value = 0;
       break;
     }
@@ -162,6 +184,18 @@ inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
   return Value;
 }
 
+/// Utility function to decode a ULEB128 value.
+///
+/// If \p n is non-null, it receives the number of bytes consumed on success.
+/// If \p end is non-null, decoding will not read at or beyond that address.
+/// If \p error is non-null, it will point to a static error message if an error
+/// occurred. It will not be modified on success.
+inline uint64_t decodeULEB128(const uint8_t *p, unsigned *n = nullptr,
+                              const uint8_t *end = nullptr,
+                              const char **error = nullptr) {
+  return decodeULEB128(p, n, end, error, nullptr);
+}
+
 /// Utility function to decode a SLEB128 value.
 ///
 /// If \p error is non-null, it will point to a static error message,
diff --git a/llvm/lib/ProfileData/SampleProfReader.cpp b/llvm/lib/ProfileData/SampleProfReader.cpp
index 8f50873c72d03..2dc5e931486f0 100644
--- a/llvm/lib/ProfileData/SampleProfReader.cpp
+++ b/llvm/lib/ProfileData/SampleProfReader.cpp
@@ -40,6 +40,7 @@
 #include <algorithm>
 #include <cstddef>
 #include <cstdint>
+#include <cstring>
 #include <limits>
 #include <memory>
 #include <system_error>
@@ -549,43 +550,57 @@ bool SampleProfileReaderText::hasFormat(const MemoryBuffer &Buffer) {
   return result;
 }
 
+/// Emit a reader diagnostic for \p ProfError and return its error code.
+static std::error_code diagnoseReaderError(const SampleProfileReader &Reader,
+                                           sampleprof_error ProfError) {
+  std::error_code EC = ProfError;
+  Reader.reportError(0, EC.message());
+  return EC;
+}
+
 template <typename T> ErrorOr<T> SampleProfileReaderBinary::readNumber() {
+  if (Data >= End)
+    return diagnoseReaderError(*this, sampleprof_error::truncated);
+
   unsigned NumBytesRead = 0;
-  uint64_t Val = decodeULEB128(Data, &NumBytesRead);
+  ULEB128DecodeError DecodeError = ULEB128DecodeError::None;
+  uint64_t Val = decodeULEB128(Data, &NumBytesRead, End, nullptr, &DecodeError);
 
-  if (Val > std::numeric_limits<T>::max()) {
-    std::error_code EC = sampleprof_error::malformed;
-    reportError(0, EC.message());
-    return EC;
-  } else if (Data + NumBytesRead > End) {
-    std::error_code EC = sampleprof_error::truncated;
-    reportError(0, EC.message());
-    return EC;
+  // Preserve the distinction between incomplete input and an invalid value.
+  switch (DecodeError) {
+  case ULEB128DecodeError::None:
+    break;
+  case ULEB128DecodeError::UnexpectedEnd:
+    return diagnoseReaderError(*this, sampleprof_error::truncated);
+  case ULEB128DecodeError::TooBig:
+    return diagnoseReaderError(*this, sampleprof_error::malformed);
   }
 
+  if (Val > std::numeric_limits<T>::max())
+    return diagnoseReaderError(*this, sampleprof_error::malformed);
+
   Data += NumBytesRead;
   return static_cast<T>(Val);
 }
 
 ErrorOr<StringRef> SampleProfileReaderBinary::readString() {
-  StringRef Str(reinterpret_cast<const char *>(Data));
-  if (Data + Str.size() + 1 > End) {
-    std::error_code EC = sampleprof_error::truncated;
-    reportError(0, EC.message());
-    return EC;
-  }
+  if (Data >= End)
+    return diagnoseReaderError(*this, sampleprof_error::truncated);
 
-  Data += Str.size() + 1;
+  const auto *Terminator = static_cast<const uint8_t *>(
+      std::memchr(Data, 0, static_cast<size_t>(End - Data)));
+  if (!Terminator)
+    return diagnoseReaderError(*this, sampleprof_error::truncated);
+
+  StringRef Str(reinterpret_cast<const char *>(Data), Terminator - Data);
+  Data = Terminator + 1;
   return Str;
 }
 
 template <typename T>
 ErrorOr<T> SampleProfileReaderBinary::readUnencodedNumber() {
-  if (Data + sizeof(T) > End) {
-    std::error_code EC = sampleprof_error::truncated;
-    reportError(0, EC.message());
-    return EC;
-  }
+  if (Data > End || static_cast<size_t>(End - Data) < sizeof(T))
+    return diagnoseReaderError(*this, sampleprof_error::truncated);
 
   using namespace support;
   T Val = endian::readNext<T, llvm::endianness::little>(Data);
@@ -1854,18 +1869,23 @@ std::error_code SampleProfileReaderBinary::readSummary() {
   return sampleprof_error::success;
 }
 
-bool SampleProfileReaderRawBinary::hasFormat(const MemoryBuffer &Buffer) {
+/// Return whether Buffer starts with ExpectedMagic without reading beyond it.
+static bool hasBinaryFormat(const MemoryBuffer &Buffer,
+                            uint64_t ExpectedMagic) {
   const uint8_t *Data =
       reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
-  uint64_t Magic = decodeULEB128(Data);
-  return Magic == SPMagic();
+  const uint8_t *End = reinterpret_cast<const uint8_t *>(Buffer.getBufferEnd());
+  ULEB128DecodeError DecodeError = ULEB128DecodeError::None;
+  uint64_t Magic = decodeULEB128(Data, nullptr, End, nullptr, &DecodeError);
+  return DecodeError == ULEB128DecodeError::None && Magic == ExpectedMagic;
+}
+
+bool SampleProfileReaderRawBinary::hasFormat(const MemoryBuffer &Buffer) {
+  return hasBinaryFormat(Buffer, SPMagic());
 }
 
 bool SampleProfileReaderExtBinary::hasFormat(const MemoryBuffer &Buffer) {
-  const uint8_t *Data =
-      reinterpret_cast<const uint8_t *>(Buffer.getBufferStart());
-  uint64_t Magic = decodeULEB128(Data);
-  return Magic == SPMagic(SPF_Ext_Binary);
+  return hasBinaryFormat(Buffer, SPMagic(SPF_Ext_Binary));
 }
 
 std::error_code SampleProfileReaderGCC::skipNextWord() {
@@ -2108,8 +2128,10 @@ std::error_code SampleProfileReaderGCC::readImpl() {
 }
 
 bool SampleProfileReaderGCC::hasFormat(const MemoryBuffer &Buffer) {
-  StringRef Magic(Buffer.getBufferStart());
-  return Magic == "adcg*704";
+  StringRef Contents = Buffer.getBuffer();
+  // Preserve exact magic matching, including a magic-only eight-byte buffer.
+  return Contents.starts_with("adcg*704") &&
+         (Contents.size() == 8 || Contents[8] == '\0');
 }
 
 void SampleProfileReaderItaniumRemapper::applyRemapping(LLVMContext &Ctx) {
diff --git a/llvm/unittests/ProfileData/SampleProfTest.cpp b/llvm/unittests/ProfileData/SampleProfTest.cpp
index fece113cc6ba6..3d8560357e2f4 100644
--- a/llvm/unittests/ProfileData/SampleProfTest.cpp
+++ b/llvm/unittests/ProfileData/SampleProfTest.cpp
@@ -9,6 +9,7 @@
 #include "llvm/ProfileData/SampleProf.h"
 #include "llvm/ADT/StringMap.h"
 #include "llvm/ADT/StringRef.h"
+#include "llvm/Config/llvm-config.h"
 #include "llvm/IR/DebugInfoMetadata.h"
 #include "llvm/IR/LLVMContext.h"
 #include "llvm/IR/Metadata.h"
@@ -16,17 +17,31 @@
 #include "llvm/ProfileData/SampleProfReader.h"
 #include "llvm/ProfileData/SampleProfWriter.h"
 #include "llvm/Support/Casting.h"
+#include "llvm/Support/Error.h"
 #include "llvm/Support/ErrorOr.h"
 #include "llvm/Support/FileSystem.h"
 #include "llvm/Support/LEB128.h"
+#include "llvm/Support/Memory.h"
 #include "llvm/Support/MemoryBuffer.h"
+#include "llvm/Support/Process.h"
 #include "llvm/Support/VirtualFileSystem.h"
 #include "llvm/Support/raw_ostream.h"
+#include "llvm/Testing/Support/Error.h"
 #include "llvm/Testing/Support/SupportHelpers.h"
 #include "gtest/gtest.h"
+#include <algorithm>
+#include <memory>
 #include <string>
+#include <system_error>
 #include <vector>
 
+#if defined(_WIN32)
+#include "llvm/Support/Windows/WindowsSupport.h"
+#include "llvm/Support/WindowsError.h"
+#elif defined(LLVM_ON_UNIX) && !defined(__MVS__)
+#include <sys/mman.h>
+#endif
+
 using namespace llvm;
 using namespace sampleprof;
 
@@ -41,6 +56,115 @@ static ::testing::AssertionResult NoError(std::error_code EC) {
 
 namespace {
 
+/// Expose string decoding for focused buffer-boundary tests.
+class StringDataTestReader final : public SampleProfileReaderRawBinary {
+public:
+  /// Construct a reader over the supplied test buffer.
+  StringDataTestReader(std::unique_ptr<MemoryBuffer> Buffer,
+                       LLVMContext &Context)
+      : SampleProfileReaderRawBinary(std::move(Buffer), Context) {}
+
+  /// Decode one string using the production binary-reader path.
+  ErrorOr<StringRef> readCString() {
+    Data = reinterpret_cast<const uint8_t *>(Buffer->getBufferStart());
+    End = reinterpret_cast<const uint8_t *>(Buffer->getBufferEnd());
+    return readString();
+  }
+
+  /// Decode one string with the cursor at the logical buffer end.
+  ErrorOr<StringRef> readCStringAtEnd() {
+    Data = reinterpret_cast<const uint8_t *>(Buffer->getBufferEnd());
+    End = Data;
+    return readString();
+  }
+
+  /// Return whether the decoder consumed the complete test buffer.
+  bool atEnd() const { return Data == End; }
+};
+
+/// Expose ExtBinary header reading for fixed-width number boundary tests.
+class ExtBinaryDataTestReader final : public SampleProfileReaderExtBinary {
+public:
+  /// Construct a reader over the supplied test buffer.
+  ExtBinaryDataTestReader(std::unique_ptr<MemoryBuffer> Buffer,
+                          LLVMContext &Context)
+      : SampleProfileReaderExtBinary(std::move(Buffer), Context) {}
+
+  /// Read the ExtBinary header using the production reader path.
+  std::error_code readProfileHeader() { return readHeader(); }
+};
+
+/// Create a non-owning MemoryBuffer view over \p Bytes.
+static std::unique_ptr<MemoryBuffer>
+createTestMemoryBuffer(ArrayRef<uint8_t> Bytes) {
+  return MemoryBuffer::getMemBuffer(
+      StringRef(reinterpret_cast<const char *>(Bytes.data()), Bytes.size()),
+      "binary-data", /*RequiresNullTerminator=*/false);
+}
+
+/// Own a buffer ending immediately before an inaccessible guard page.
+struct GuardedMemoryBuffer {
+  sys::OwningMemoryBlock Storage;
+  std::unique_ptr<MemoryBuffer> Buffer;
+};
+
+/// Return whether this host exposes an API for no-access memory pages.
+static constexpr bool guardPagesSupported() {
+#if defined(_WIN32) || (defined(LLVM_ON_UNIX) && !defined(__MVS__))
+  return true;
+#else
+  return false;
+#endif
+}
+
+/// Make \p GuardPage inaccessible so an out-of-bounds read faults immediately.
+static std::error_code protectGuardPage(sys::MemoryBlock GuardPage) {
+#if defined(_WIN32)
+  DWORD OldProtection;
+  if (!VirtualProtect(GuardPage.base(), GuardPage.allocatedSize(),
+                      PAGE_NOACCESS, &OldProtection))
+    return mapLastWindowsError();
+  return std::error_code();
+#elif defined(LLVM_ON_UNIX) && !defined(__MVS__)
+  if (::mprotect(GuardPage.base(), GuardPage.allocatedSize(), PROT_NONE))
+    return errnoAsErrorCode();
+  return std::error_code();
+#else
+  return std::make_error_code(std::errc::not_supported);
+#endif
+}
+
+/// Copy \p Bytes to the end of a readable page followed by a no-access page.
+static Expected<GuardedMemoryBuffer>
+createGuardedMemoryBuffer(ArrayRef<uint8_t> Bytes) {
+  auto PageSizeOrErr = sys::Process::getPageSize();
+  if (!PageSizeOrErr)
+    return PageSizeOrErr.takeError();
+  const size_t PageSize = *PageSizeOrErr;
+  if (Bytes.empty() || Bytes.size() > PageSize)
+    return createStringError(std::errc::invalid_argument,
+                             "test buffer must fit in one memory page");
+
+  std::error_code EC;
+  sys::MemoryBlock Allocation = sys::Memory::allocateMappedMemory(
+      2 * PageSize, nullptr, sys::Memory::MF_READ | sys::Memory::MF_WRITE, EC);
+  if (EC)
+    return errorCodeToError(EC);
+
+  sys::OwningMemoryBlock Storage(Allocation);
+  auto *GuardStart = static_cast<uint8_t *>(Allocation.base()) + PageSize;
+  sys::MemoryBlock GuardPage(GuardStart, PageSize);
+  EC = protectGuardPage(GuardPage);
+  if (EC)
+    return errorCodeToError(EC);
+
+  auto *BufferStart = GuardStart - Bytes.size();
+  std::copy(Bytes.begin(), Bytes.end(), BufferStart);
+  auto Buffer =
+      createTestMemoryBuffer(ArrayRef<uint8_t>(BufferStart, Bytes.size()));
+  return GuardedMemoryBuffer{std::move(Storage), std::move(Buffer)};
+}
+
 struct SampleProfTest : ::testing::Test {
   LLVMContext Context;
   std::unique_ptr<SampleProfileWriter> Writer;
@@ -177,6 +301,46 @@ struct SampleProfTest : ::testing::Test {
     return Buffer;
   }
 
+  // Read a binary header through the production reader path.
+  std::error_code readBinaryHeaderFromBuffer(ArrayRef<uint8_t> Encoded) {
+    auto Buffer = createTestMemoryBuffer(Encoded);
+    SampleProfileReaderRawBinary TestReader(std::move(Buffer), Context);
+    return TestReader.readHeader();
+  }
+
+  // Decode one string through the production reader path.
+  std::error_code readStringErrorFromBuffer(ArrayRef<uint8_t> Encoded) {
+    auto Buffer = createTestMemoryBuffer(Encoded);
+    StringDataTestReader TestReader(std::move(Buffer), Context);
+    return TestReader.readCString().getError();
+  }
+
+  // Decode one string with the cursor at the supplied buffer's end.
+  std::error_code readStringAtEndError(ArrayRef<uint8_t> Encoded) {
+    auto Buffer = createTestMemoryBuffer(Encoded);
+    StringDataTestReader TestReader(std::move(Buffer), Context);
+    return TestReader.readCStringAtEnd().getError();
+  }
+
+  // Build an ExtBinary header whose section-count field is truncated mid-value.
+  SmallVector<uint8_t, 32> writeTruncatedExtBinarySecCount() {
+    SmallVector<char, 32> Header;
+    raw_svector_ostream OS(Header);
+    encodeULEB128(SPMagic(SPF_Ext_Binary), OS);
+    encodeULEB128(103, OS);
+    SmallVector<uint8_t, 32> Encoded(Header.begin(), Header.end());
+    // Four bytes are not enough for the fixed-width section-count uint64_t.
+    Encoded.append({1, 2, 3, 4});
+    return Encoded;
+  }
+
+  // Read an ExtBinary header through the production reader path.
+  std::error_code readExtBinaryHeaderFromBuffer(ArrayRef<uint8_t> Encoded) {
+    auto Buffer = createTestMemoryBuffer(Encoded);
+    ExtBinaryDataTestReader TestReader(std::move(Buffer), Context);
+    return TestReader.readProfileHeader();
+  }
+
   // Read the profile from an in-memory buffer, verify its payload, and
   // return the format version.
   ErrorOr<uint64_t> readVersionFromBuffer(ArrayRef<char> Buffer) {
@@ -470,6 +634,49 @@ struct SampleProfTest : ::testing::Test {
   }
 };
 
+/// Run buffer-boundary tests only where the host supports inaccessible pages.
+struct GuardedSampleProfTest : SampleProfTest {
+  /// Skip guard-page tests on hosts without the required memory protection.
+  void SetUp() override {
+    SampleProfTest::SetUp();
+    if (!guardPagesSupported())
+      GTEST_SKIP() << "guard pages are not supported on this host";
+  }
+
+  /// Read a binary header ending at a guard page.
+  Expected<std::error_code>
+  readBinaryHeaderFromGuardedBuffer(ArrayRef<uint8_t> Encoded) {
+    auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+    if (!GuardedOrErr)
+      return GuardedOrErr.takeError();
+    GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+    SampleProfileReaderRawBinary TestReader(std::move(Guarded.Buffer), Context);
+    return TestReader.readHeader();
+  }
+
+  /// Decode one string ending at a guard page.
+  Expected<std::error_code>
+  readStringErrorFromGuardedBuffer(ArrayRef<uint8_t> Encoded) {
+    auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+    if (!GuardedOrErr)
+      return GuardedOrErr.takeError();
+    GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+    StringDataTestReader TestReader(std::move(Guarded.Buffer), Context);
+    return TestReader.readCString().getError();
+  }
+
+  /// Read an ExtBinary header ending at a guard page.
+  Expected<std::error_code>
+  readExtBinaryHeaderFromGuardedBuffer(ArrayRef<uint8_t> Encoded) {
+    auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+    if (!GuardedOrErr)
+      return GuardedOrErr.takeError();
+    GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+    ExtBinaryDataTestReader TestReader(std::move(Guarded.Buffer), Context);
+    return TestReader.readProfileHeader();
+  }
+};
+
 TEST_F(SampleProfTest, roundtrip_text_profile) {
   testRoundTrip(SampleProfileFormat::SPF_Text, false, false);
 }
@@ -613,6 +820,125 @@ TEST(SampleProfSectionFlagTest, RejectsMismatchedSectionSpecificFlag) {
 }
 #endif
 
+// Verify that a non-terminated ULEB is reported as truncated.
+TEST_F(SampleProfTest, TruncatedULEBIsTruncated) {
+  const uint8_t Encoded[] = {0x80};
+  EXPECT_EQ(readBinaryHeaderFromBuffer(Encoded), sampleprof_error::truncated);
+}
+
+// Verify that a non-terminated ULEB at a guard-page boundary does not read past
+// the supplied MemoryBuffer.
+TEST_F(GuardedSampleProfTest, TruncatedULEBDoesNotReadPastBufferEnd) {
+  const uint8_t Encoded[] = {0x80};
+  auto ECOrErr = readBinaryHeaderFromGuardedBuffer(Encoded);
+  ASSERT_THAT_EXPECTED(ECOrErr, Succeeded());
+  EXPECT_EQ(*ECOrErr, sampleprof_error::truncated);
+}
+
+// Verify that an in-bounds ULEB value exceeding uint64_t is malformed rather
+// than silently decoded as zero.
+TEST_F(SampleProfTest, OversizedULEBIsMalformed) {
+  SmallVector<uint8_t, 10> Encoded(10, 0xff);
+  EXPECT_EQ(readBinaryHeaderFromBuffer(Encoded), sampleprof_error::malformed);
+}
+
+// Verify that format detection rejects a truncated magic value.
+TEST_F(SampleProfTest, TruncatedMagicIsRejected) {
+  const uint8_t Encoded[] = {0x80};
+  auto Buffer = createTestMemoryBuffer(Encoded);
+  EXPECT_FALSE(SampleProfileReaderRawBinary::hasFormat(*Buffer));
+  EXPECT_FALSE(SampleProfileReaderExtBinary::hasFormat(*Buffer));
+}
+
+// Verify that format detection rejects a truncated magic without reading into
+// the guard page beyond the supplied MemoryBuffer.
+TEST_F(GuardedSampleProfTest, TruncatedMagicDoesNotReadPastBufferEnd) {
+  const uint8_t Encoded[] = {0x80};
+  auto GuardedOrErr = createGuardedMemoryBuffer(Encoded);
+  ASSERT_THAT_EXPECTED(GuardedOrErr, Succeeded());
+  GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+  EXPECT_FALSE(SampleProfileReaderRawBinary::hasFormat(*Guarded.Buffer));
+  EXPECT_FALSE(SampleProfileReaderExtBinary::hasFormat(*Guarded.Buffer));
+}
+
+// Verify all accepted and rejected forms of the GCC format magic.
+TEST_F(SampleProfTest, GCCMagicDetection) {
+  const uint8_t Truncated[] = {'a'};
+  auto ShortBuffer = createTestMemoryBuffer(Truncated);
+  EXPECT_FALSE(SampleProfileReaderGCC::hasFormat(*ShortBuffer));
+
+  const uint8_t MagicOnly[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4'};
+  auto MagicOnlyBuffer = createTestMemoryBuffer(MagicOnly);
+  EXPECT_TRUE(SampleProfileReaderGCC::hasFormat(*MagicOnlyBuffer));
+
+  const uint8_t Magic[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4', 0};
+  auto MagicBuffer = createTestMemoryBuffer(Magic);
+  EXPECT_TRUE(SampleProfileReaderGCC::hasFormat(*MagicBuffer));
+
+  const uint8_t NonTerminated[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4', 'x'};
+  auto NonTerminatedBuffer = createTestMemoryBuffer(NonTerminated);
+  EXPECT_FALSE(SampleProfileReaderGCC::hasFormat(*NonTerminatedBuffer));
+}
+
+// Verify that GCC magic detection does not read beyond a magic-only buffer.
+TEST_F(GuardedSampleProfTest, GCCMagicDoesNotReadPastBufferEnd) {
+  const uint8_t MagicOnly[] = {'a', 'd', 'c', 'g', '*', '7', '0', '4'};
+  auto GuardedOrErr = createGuardedMemoryBuffer(MagicOnly);
+  ASSERT_THAT_EXPECTED(GuardedOrErr, Succeeded());
+  GuardedMemoryBuffer Guarded = std::move(*GuardedOrErr);
+  EXPECT_TRUE(SampleProfileReaderGCC::hasFormat(*Guarded.Buffer));
+}
+
+// Verify that an unterminated string is reported as truncated.
+TEST_F(SampleProfTest, UnterminatedStringIsTruncated) {
+  const uint8_t Encoded[] = {'n', 'o'};
+  EXPECT_EQ(readStringErrorFromBuffer(Encoded), sampleprof_error::truncated);
+}
+
+// Verify that an unterminated string does not search into the guard page beyond
+// the supplied MemoryBuffer.
+TEST_F(GuardedSampleProfTest, UnterminatedStringDoesNotReadPastBufferEnd) {
+  const uint8_t Encoded[] = {'n', 'o'};
+  auto ECOrErr = readStringErrorFromGuardedBuffer(Encoded);
+  ASSERT_THAT_EXPECTED(ECOrErr, Succeeded());
+  EXPECT_EQ(*ECOrErr, sampleprof_error::truncated);
+}
+
+// Verify that readString accepts a terminator in the final buffer byte and
+// advances the input cursor exactly to End.
+TEST_F(SampleProfTest, ValidStringEndsAtBufferEnd) {
+  const uint8_t Encoded[] = {'o', 'k', 0};
+  auto Buffer = createTestMemoryBuffer(Encoded);
+  StringDataTestReader TestReader(std::move(Buffer), Context);
+
+  auto StringOrErr = TestReader.readCString();
+  ASSERT_TRUE(NoError(StringOrErr.getError()));
+  EXPECT_EQ(*StringOrErr, "ok");
+  EXPECT_TRUE(TestReader.atEnd());
+}
+
+// Verify that string decoding rejects a cursor already at the buffer end.
+TEST_F(SampleProfTest, StringReaderAtEndIsTruncated) {
+  const uint8_t Data[] = {0};
+  EXPECT_EQ(readStringAtEndError(Data), sampleprof_error::truncated);
+}
+
+// Verify that a short fixed-width ExtBinary section count is truncated.
+TEST_F(SampleProfTest, ShortUnencodedNumberIsTruncated) {
+  auto Encoded = writeTruncatedExtBinarySecCount();
+  EXPECT_EQ(readExtBinaryHeaderFromBuffer(Encoded),
+            sampleprof_error::truncated);
+}
+
+// Verify that a short fixed-width ExtBinary section count does not read into
+// the guard page.
+TEST_F(GuardedSampleProfTest, ShortUnencodedNumberDoesNotReadPastBufferEnd) {
+  auto Encoded = writeTruncatedExtBinarySecCount();
+  auto ECOrErr = readExtBinaryHeaderFromGuardedBuffer(Encoded);
+  ASSERT_THAT_EXPECTED(ECOrErr, Succeeded());
+  EXPECT_EQ(*ECOrErr, sampleprof_error::truncated);
+}
+
 // Verify that requesting format version 103 results in a version 103 profile.
 TEST_F(SampleProfTest, SampleProfileFormatVersion103) {
   auto BufferOrErr = writeProfileToBuffer(103);
diff --git a/llvm/unittests/Support/LEB128Test.cpp b/llvm/unittests/Support/LEB128Test.cpp
index dc80f7c4e71ea..e47989d8cf4c9 100644
--- a/llvm/unittests/Support/LEB128Test.cpp
+++ b/llvm/unittests/Support/LEB128Test.cpp
@@ -179,6 +179,29 @@ TEST(LEB128Test, DecodeInvalidULEB128) {
 #undef EXPECT_INVALID_ULEB128
 }
 
+TEST(LEB128Test, DecodeULEB128ErrorCode) {
+  // Report an encoding that reaches the buffer end before its terminating byte.
+  const uint8_t Truncated[] = {0x80};
+  ULEB128DecodeError ErrorCode = ULEB128DecodeError::None;
+  EXPECT_EQ(0u, decodeULEB128(Truncated, nullptr, Truncated + 1, nullptr,
+                              &ErrorCode));
+  EXPECT_EQ(ULEB128DecodeError::UnexpectedEnd, ErrorCode);
+
+  // Report an in-buffer encoding whose value does not fit in uint64_t.
+  const uint8_t TooBig[] = {0x80, 0x80, 0x80, 0x80, 0x80,
+                            0x80, 0x80, 0x80, 0x80, 0x02};
+  ErrorCode = ULEB128DecodeError::None;
+  EXPECT_EQ(0u,
+            decodeULEB128(TooBig, nullptr, TooBig + 10, nullptr, &ErrorCode));
+  EXPECT_EQ(ULEB128DecodeError::TooBig, ErrorCode);
+
+  // Clear a previous error when decoding succeeds.
+  const uint8_t Valid[] = {0x01};
+  ErrorCode = ULEB128DecodeError::TooBig;
+  EXPECT_EQ(1u, decodeULEB128(Valid, nullptr, Valid + 1, nullptr, &ErrorCode));
+  EXPECT_EQ(ULEB128DecodeError::None, ErrorCode);
+}
+
 TEST(LEB128Test, DecodeSLEB128) {
 #define EXPECT_DECODE_SLEB128_EQ(EXPECTED, VALUE) \
   do { \



More information about the llvm-commits mailing list