Index: lib/scudo/standalone/CMakeLists.txt =================================================================== --- lib/scudo/standalone/CMakeLists.txt +++ lib/scudo/standalone/CMakeLists.txt @@ -60,6 +60,7 @@ atomic_helpers.h bytemap.h checksum.h + chunk.h flags.h flags_parser.h fuchsia.h Index: lib/scudo/standalone/checksum.h =================================================================== --- lib/scudo/standalone/checksum.h +++ lib/scudo/standalone/checksum.h @@ -28,8 +28,8 @@ namespace scudo { -enum ChecksumType : u8 { - BSDChecksum = 0, +enum class Checksum : u8 { + BSD = 0, HardwareCRC32 = 1, }; Index: lib/scudo/standalone/checksum.cc =================================================================== --- lib/scudo/standalone/checksum.cc +++ 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. Index: lib/scudo/standalone/chunk.h =================================================================== --- lib/scudo/standalone/chunk.h +++ 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(CRC32_INTRINSIC(Seed, Value)); + for (uptr I = 0; I < ArraySize; I++) + Crc = static_cast(CRC32_INTRINSIC(Crc, Array[I])); + return static_cast((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((Crc & 0xffff) ^ (Crc >> 16)); + } else { + u16 Checksum = computeBSDChecksum(static_cast(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(reinterpret_cast(Ptr) - + getHeaderSize()); +} + +INLINE +const AtomicPackedHeader *getConstAtomicHeader(const void *Ptr) { + return reinterpret_cast( + reinterpret_cast(Ptr) - getHeaderSize()); +} + +INLINE void *getBlockBegin(const void *Ptr, UnpackedHeader *Header) { + return reinterpret_cast(reinterpret_cast(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(Ptr), HeaderHolder, + ARRAY_SIZE(HeaderHolder)); +} + +INLINE void storeHeader(u32 Cookie, void *Ptr, + UnpackedHeader *NewUnpackedHeader) { + NewUnpackedHeader->Checksum = + computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader); + PackedHeader NewPackedHeader = bit_cast(*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(NewPackedHeader); + if (UNLIKELY(NewUnpackedHeader->Checksum != + computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader))) + reportHeaderCorruption(const_cast(Ptr)); +} + +INLINE void compareExchangeHeader(u32 Cookie, void *Ptr, + UnpackedHeader *NewUnpackedHeader, + UnpackedHeader *OldUnpackedHeader) { + NewUnpackedHeader->Checksum = + computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader); + PackedHeader NewPackedHeader = bit_cast(*NewUnpackedHeader); + PackedHeader OldPackedHeader = bit_cast(*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(NewPackedHeader); + return NewUnpackedHeader->Checksum == + computeHeaderChecksum(Cookie, Ptr, NewUnpackedHeader); +} + +} // namespace Chunk + +} // namespace scudo + +#endif // SCUDO_CHUNK_H_ Index: lib/scudo/standalone/tests/CMakeLists.txt =================================================================== --- lib/scudo/standalone/tests/CMakeLists.txt +++ lib/scudo/standalone/tests/CMakeLists.txt @@ -52,6 +52,7 @@ atomic_test.cc bytemap_test.cc checksum_test.cc + chunk_test.cc flags_test.cc list_test.cc map_test.cc Index: lib/scudo/standalone/tests/chunk_test.cc =================================================================== --- lib/scudo/standalone/tests/chunk_test.cc +++ 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 + +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(reinterpret_cast(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(reinterpret_cast(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(reinterpret_cast(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(Block) + I) ^= 0x42U; + EXPECT_DEATH(scudo::Chunk::loadHeader(Cookie, P, &Header), ""); + *(reinterpret_cast(Block) + I) ^= 0x42U; + } + free(Block); +}