summaryrefslogtreecommitdiff
path: root/lib/scudo/standalone
diff options
context:
space:
mode:
authorKostya Kortchinsky <kostyak@google.com>2019-05-08 21:54:02 +0000
committerKostya Kortchinsky <kostyak@google.com>2019-05-08 21:54:02 +0000
commit024605807fd5d0f64b096d160d4c68434e32711a (patch)
tree5d6d737d240a38e1ab7300ff4c2f31f6d2884769 /lib/scudo/standalone
parent177b648688e0f497bba7ebe54804b530762b0371 (diff)
downloadcompiler-rt-024605807fd5d0f64b096d160d4c68434e32711a.tar.gz
[scudo][standalone] Introduce the chunk header
Summary: ... and its related functions. The structure and its functionalities are identical to existing ones. The header stores information on a `scudo::Chunk` to be able to detect inconsitencies or potential corruption attempts. It is checksummed for that purpose. Reviewers: morehouse, eugenis, vitalybuka, hctim Reviewed By: vitalybuka Subscribers: mgorny, delcypher, jfb, #sanitizers, llvm-commits Tags: #llvm, #sanitizers Differential Revision: https://reviews.llvm.org/D61654 git-svn-id: https://llvm.org/svn/llvm-project/compiler-rt/trunk@360290 91177308-0d34-0410-b5e6-96231b3b80d8
Diffstat (limited to 'lib/scudo/standalone')
-rw-r--r--lib/scudo/standalone/CMakeLists.txt1
-rw-r--r--lib/scudo/standalone/checksum.cc2
-rw-r--r--lib/scudo/standalone/checksum.h4
-rw-r--r--lib/scudo/standalone/chunk.h162
-rw-r--r--lib/scudo/standalone/tests/CMakeLists.txt1
-rw-r--r--lib/scudo/standalone/tests/chunk_test.cc82
6 files changed, 249 insertions, 3 deletions
diff --git a/lib/scudo/standalone/CMakeLists.txt b/lib/scudo/standalone/CMakeLists.txt
index 3b6963857..3313283de 100644
--- a/lib/scudo/standalone/CMakeLists.txt
+++ b/lib/scudo/standalone/CMakeLists.txt
@@ -60,6 +60,7 @@ set(SCUDO_HEADERS
atomic_helpers.h
bytemap.h
checksum.h
+ chunk.h
flags.h
flags_parser.h
fuchsia.h
diff --git a/lib/scudo/standalone/checksum.cc b/lib/scudo/standalone/checksum.cc
index ff6462bcd..0896d5bdc 100644
--- a/lib/scudo/standalone/checksum.cc
+++ b/lib/scudo/standalone/checksum.cc
@@ -22,7 +22,7 @@
namespace scudo {
-atomic_u8 HashAlgorithm = {BSDChecksum};
+Checksum HashAlgorithm = {Checksum::BSD};
#if defined(__x86_64__) || defined(__i386__)
// i386 and x86_64 specific code to detect CRC32 hardware support via CPUID.
diff --git a/lib/scudo/standalone/checksum.h b/lib/scudo/standalone/checksum.h
index 7c4afcd96..092342fd6 100644
--- a/lib/scudo/standalone/checksum.h
+++ b/lib/scudo/standalone/checksum.h
@@ -28,8 +28,8 @@
namespace scudo {
-enum ChecksumType : u8 {
- BSDChecksum = 0,
+enum class Checksum : u8 {
+ BSD = 0,
HardwareCRC32 = 1,
};
diff --git a/lib/scudo/standalone/chunk.h b/lib/scudo/standalone/chunk.h
new file mode 100644
index 000000000..ec0b0ee8b
--- /dev/null
+++ b/lib/scudo/standalone/chunk.h
@@ -0,0 +1,162 @@
+//===-- chunk.h -------------------------------------------------*- 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef SCUDO_CHUNK_H_
+#define SCUDO_CHUNK_H_
+
+#include "platform.h"
+
+#include "atomic_helpers.h"
+#include "checksum.h"
+#include "common.h"
+#include "report.h"
+
+namespace scudo {
+
+extern Checksum HashAlgorithm;
+
+INLINE u16 computeChecksum(u32 Seed, uptr Value, uptr *Array, uptr ArraySize) {
+ // If the hardware CRC32 feature is defined here, it was enabled everywhere,
+ // as opposed to only for crc32_hw.cc. This means that other hardware specific
+ // instructions were likely emitted at other places, and as a result there is
+ // no reason to not use it here.
+#if defined(__SSE4_2__) || defined(__ARM_FEATURE_CRC32)
+ u32 Crc = static_cast<u32>(CRC32_INTRINSIC(Seed, Value));
+ for (uptr I = 0; I < ArraySize; I++)
+ Crc = static_cast<u32>(CRC32_INTRINSIC(Crc, Array[I]));
+ return static_cast<u16>((Crc & 0xffff) ^ (Crc >> 16));
+#else
+ if (HashAlgorithm == Checksum::HardwareCRC32) {
+ u32 Crc = computeHardwareCRC32(Seed, Value);
+ for (uptr I = 0; I < ArraySize; I++)
+ Crc = computeHardwareCRC32(Crc, Array[I]);
+ return static_cast<u16>((Crc & 0xffff) ^ (Crc >> 16));
+ } else {
+ u16 Checksum = computeBSDChecksum(static_cast<u16>(Seed & 0xffff), Value);
+ for (uptr I = 0; I < ArraySize; I++)
+ Checksum = computeBSDChecksum(Checksum, Array[I]);
+ return Checksum;
+ }
+#endif // defined(__SSE4_2__) || defined(__ARM_FEATURE_CRC32)
+}
+
+namespace Chunk {
+
+// Note that in an ideal world, `State` and `Origin` should be `enum class`, and
+// the associated `UnpackedHeader` fields of their respective enum class type
+// but https://gcc.gnu.org/bugzilla/show_bug.cgi?id=61414 prevents it from
+// happening, as it will error, complaining the number of bits is not enough.
+enum Origin : u8 {
+ Malloc = 0,
+ New = 1,
+ NewArray = 2,
+ Memalign = 3,
+};
+
+enum State : u8 { Available = 0, Allocated = 1, Quarantined = 2 };
+
+typedef u64 PackedHeader;
+// Update the 'Mask' constants to reflect changes in this structure.
+struct UnpackedHeader {
+ u64 Checksum : 16;
+ u64 ClassId : 8;
+ u64 SizeOrUnusedBytes : 20;
+ u8 State : 2;
+ u8 Origin : 2;
+ u64 Offset : 16;
+};
+typedef atomic_u64 AtomicPackedHeader;
+COMPILER_CHECK(sizeof(UnpackedHeader) == sizeof(PackedHeader));
+
+// Those constants are required to silence some -Werror=conversion errors when
+// assigning values to the related bitfield variables.
+constexpr uptr ChecksumMask = (1UL << 16) - 1;
+constexpr uptr ClassIdMask = (1UL << 8) - 1;
+constexpr uptr SizeOrUnusedBytesMask = (1UL << 20) - 1;
+constexpr uptr StateMask = (1UL << 2) - 1;
+constexpr uptr OriginMask = (1UL << 2) - 1;
+constexpr uptr OffsetMask = (1UL << 16) - 1;
+
+constexpr uptr getHeaderSize() {
+ return roundUpTo(sizeof(PackedHeader), 1U << SCUDO_MIN_ALIGNMENT_LOG);
+}
+
+INLINE AtomicPackedHeader *getAtomicHeader(void *Ptr) {
+ return reinterpret_cast<AtomicPackedHeader *>(reinterpret_cast<uptr>(Ptr) -
+ getHeaderSize());
+}
+
+INLINE
+const AtomicPackedHeader *getConstAtomicHeader(const void *Ptr) {
+ return reinterpret_cast<const AtomicPackedHeader *>(
+ reinterpret_cast<uptr>(Ptr) - getHeaderSize());
+}
+
+INLINE void *getBlockBegin(const void *Ptr, UnpackedHeader *Header) {
+ return reinterpret_cast<void *>(reinterpret_cast<uptr>(Ptr) -
+ getHeaderSize() -
+ (Header->Offset << SCUDO_MIN_ALIGNMENT_LOG));
+}
+
+// We do not need a cryptographically strong hash for the checksum, but a CRC
+// type function that can alert us in the event a header is invalid or
+// corrupted. Ideally slightly better than a simple xor of all fields.
+static INLINE u16 computeHeaderChecksum(u32 Cookie, const void *Ptr,
+ UnpackedHeader *Header) {
+ UnpackedHeader ZeroChecksumHeader = *Header;
+ ZeroChecksumHeader.Checksum = 0;
+ uptr HeaderHolder[sizeof(UnpackedHeader) / sizeof(uptr)];
+ memcpy(&HeaderHolder, &ZeroChecksumHeader, sizeof(HeaderHolder));
+ return computeChecksum(Cookie, reinterpret_cast<uptr>(Ptr), HeaderHolder,
+ ARRAY_SIZE(HeaderHolder));
+}
+
+INLINE void storeHeader(u32 Cookie, void *Ptr,
+ UnpackedHeader *NewUnpackedHeader) {
+ NewUnpackedHeader->Checksum =
+ computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader);
+ PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader);
+ atomic_store_relaxed(getAtomicHeader(Ptr), NewPackedHeader);
+}
+
+INLINE
+void loadHeader(u32 Cookie, const void *Ptr,
+ UnpackedHeader *NewUnpackedHeader) {
+ PackedHeader NewPackedHeader = atomic_load_relaxed(getConstAtomicHeader(Ptr));
+ *NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader);
+ if (UNLIKELY(NewUnpackedHeader->Checksum !=
+ computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader)))
+ reportHeaderCorruption(const_cast<void *>(Ptr));
+}
+
+INLINE void compareExchangeHeader(u32 Cookie, void *Ptr,
+ UnpackedHeader *NewUnpackedHeader,
+ UnpackedHeader *OldUnpackedHeader) {
+ NewUnpackedHeader->Checksum =
+ computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader);
+ PackedHeader NewPackedHeader = bit_cast<PackedHeader>(*NewUnpackedHeader);
+ PackedHeader OldPackedHeader = bit_cast<PackedHeader>(*OldUnpackedHeader);
+ if (UNLIKELY(!atomic_compare_exchange_strong(
+ getAtomicHeader(Ptr), &OldPackedHeader, NewPackedHeader,
+ memory_order_relaxed)))
+ reportHeaderRace(Ptr);
+}
+
+INLINE
+bool isValid(u32 Cookie, const void *Ptr, UnpackedHeader *NewUnpackedHeader) {
+ PackedHeader NewPackedHeader = atomic_load_relaxed(getConstAtomicHeader(Ptr));
+ *NewUnpackedHeader = bit_cast<UnpackedHeader>(NewPackedHeader);
+ return NewUnpackedHeader->Checksum ==
+ computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader);
+}
+
+} // namespace Chunk
+
+} // namespace scudo
+
+#endif // SCUDO_CHUNK_H_
diff --git a/lib/scudo/standalone/tests/CMakeLists.txt b/lib/scudo/standalone/tests/CMakeLists.txt
index 548f371bf..cbd87f393 100644
--- a/lib/scudo/standalone/tests/CMakeLists.txt
+++ b/lib/scudo/standalone/tests/CMakeLists.txt
@@ -52,6 +52,7 @@ set(SCUDO_UNIT_TEST_SOURCES
atomic_test.cc
bytemap_test.cc
checksum_test.cc
+ chunk_test.cc
flags_test.cc
list_test.cc
map_test.cc
diff --git a/lib/scudo/standalone/tests/chunk_test.cc b/lib/scudo/standalone/tests/chunk_test.cc
new file mode 100644
index 000000000..fea975b84
--- /dev/null
+++ b/lib/scudo/standalone/tests/chunk_test.cc
@@ -0,0 +1,82 @@
+//===-- chunk_test.cc -------------------------------------------*- 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 "chunk.h"
+
+#include "gtest/gtest.h"
+
+#include <stdlib.h>
+
+static constexpr scudo::uptr HeaderSize = scudo::Chunk::getHeaderSize();
+static constexpr scudo::u32 Cookie = 0x41424344U;
+static constexpr scudo::u32 InvalidCookie = 0x11223344U;
+
+static void initChecksum(void) {
+ if (&scudo::computeHardwareCRC32 && scudo::hasHardwareCRC32())
+ scudo::HashAlgorithm = scudo::Checksum::HardwareCRC32;
+}
+
+TEST(ScudoChunkTest, ChunkBasic) {
+ initChecksum();
+ const scudo::uptr Size = 0x100U;
+ scudo::Chunk::UnpackedHeader Header = {};
+ void *Block = malloc(HeaderSize + Size);
+ void *P = reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(Block) +
+ HeaderSize);
+ scudo::Chunk::storeHeader(Cookie, P, &Header);
+ memset(P, 'A', Size);
+ EXPECT_EQ(scudo::Chunk::getBlockBegin(P, &Header), Block);
+ scudo::Chunk::loadHeader(Cookie, P, &Header);
+ EXPECT_TRUE(scudo::Chunk::isValid(Cookie, P, &Header));
+ EXPECT_FALSE(scudo::Chunk::isValid(InvalidCookie, P, &Header));
+ EXPECT_DEATH(scudo::Chunk::loadHeader(InvalidCookie, P, &Header), "");
+ free(Block);
+}
+
+TEST(ScudoChunkTest, ChunkCmpXchg) {
+ initChecksum();
+ const scudo::uptr Size = 0x100U;
+ scudo::Chunk::UnpackedHeader OldHeader = {};
+ OldHeader.Origin = scudo::Chunk::Origin::Malloc;
+ OldHeader.ClassId = 0x42U;
+ OldHeader.SizeOrUnusedBytes = Size;
+ OldHeader.State = scudo::Chunk::State::Allocated;
+ void *Block = malloc(HeaderSize + Size);
+ void *P = reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(Block) +
+ HeaderSize);
+ scudo::Chunk::storeHeader(Cookie, P, &OldHeader);
+ memset(P, 'A', Size);
+ scudo::Chunk::UnpackedHeader NewHeader = OldHeader;
+ NewHeader.State = scudo::Chunk::State::Quarantined;
+ scudo::Chunk::compareExchangeHeader(Cookie, P, &NewHeader, &OldHeader);
+ NewHeader = {};
+ EXPECT_TRUE(scudo::Chunk::isValid(Cookie, P, &NewHeader));
+ EXPECT_EQ(NewHeader.State, scudo::Chunk::State::Quarantined);
+ EXPECT_FALSE(scudo::Chunk::isValid(InvalidCookie, P, &NewHeader));
+ free(Block);
+}
+
+TEST(ScudoChunkTest, CorruptHeader) {
+ initChecksum();
+ const scudo::uptr Size = 0x100U;
+ scudo::Chunk::UnpackedHeader Header = {};
+ void *Block = malloc(HeaderSize + Size);
+ void *P = reinterpret_cast<void *>(reinterpret_cast<scudo::uptr>(Block) +
+ HeaderSize);
+ scudo::Chunk::storeHeader(Cookie, P, &Header);
+ memset(P, 'A', Size);
+ EXPECT_EQ(scudo::Chunk::getBlockBegin(P, &Header), Block);
+ scudo::Chunk::loadHeader(Cookie, P, &Header);
+ // Simulate a couple of corrupted bits per byte of header data.
+ for (scudo::uptr I = 0; I < sizeof(scudo::Chunk::PackedHeader); I++) {
+ *(reinterpret_cast<scudo::u8 *>(Block) + I) ^= 0x42U;
+ EXPECT_DEATH(scudo::Chunk::loadHeader(Cookie, P, &Header), "");
+ *(reinterpret_cast<scudo::u8 *>(Block) + I) ^= 0x42U;
+ }
+ free(Block);
+}