diff --git a/compiler-rt/lib/scudo/standalone/memtag.h b/compiler-rt/lib/scudo/standalone/memtag.h --- a/compiler-rt/lib/scudo/standalone/memtag.h +++ b/compiler-rt/lib/scudo/standalone/memtag.h @@ -172,6 +172,7 @@ inline uptr addFixedTag(uptr Ptr, uptr Tag) { DCHECK_LT(Tag, 16); + DCHECK_EQ(untagPointer(Ptr), Ptr); return Ptr | (Tag << 56); } diff --git a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt --- a/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt +++ b/compiler-rt/lib/scudo/standalone/tests/CMakeLists.txt @@ -88,6 +88,7 @@ flags_test.cpp list_test.cpp map_test.cpp + memtag_test.cpp mutex_test.cpp primary_test.cpp quarantine_test.cpp diff --git a/compiler-rt/lib/scudo/standalone/tests/memtag_test.cpp b/compiler-rt/lib/scudo/standalone/tests/memtag_test.cpp new file mode 100644 --- /dev/null +++ b/compiler-rt/lib/scudo/standalone/tests/memtag_test.cpp @@ -0,0 +1,186 @@ +//===-- memtag_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 "common.h" +#include "memtag.h" +#include "platform.h" +#include "tests/scudo_unit_test.h" + +#if SCUDO_LINUX +namespace scudo { + +TEST(MemtagBasicTest, Unsupported) { + if (archSupportsMemoryTagging()) + GTEST_SKIP(); + + EXPECT_DEATH(archMemoryTagGranuleSize(), "not supported"); + EXPECT_DEATH(untagPointer((uptr)0), "not supported"); + EXPECT_DEATH(extractTag((uptr)0), "not supported"); + + EXPECT_DEATH(systemSupportsMemoryTagging(), "not supported"); + EXPECT_DEATH(systemDetectsMemoryTagFaultsTestOnly(), "not supported"); + EXPECT_DEATH(enableSystemMemoryTaggingTestOnly(), "not supported"); + + EXPECT_DEATH(selectRandomTag((uptr)0, 0), "not supported"); + EXPECT_DEATH(addFixedTag((uptr)0, 1), "not supported"); + EXPECT_DEATH(storeTags((uptr)0, (uptr)0 + sizeof(0)), "not supported"); + EXPECT_DEATH(storeTag((uptr)0), "not supported"); + EXPECT_DEATH(loadTag((uptr)0), "not supported"); + + EXPECT_DEATH(setRandomTag(nullptr, 64, 0, nullptr, nullptr), "not supported"); + EXPECT_DEATH(untagPointer(nullptr), "not supported"); + EXPECT_DEATH(loadTag(nullptr), "not supported"); + EXPECT_DEATH(addFixedTag(nullptr, 0), "not supported"); +} + +class MemtagTest : public ::testing::Test { +protected: + void SetUp() override { + if (!archSupportsMemoryTagging() || !systemDetectsMemoryTagFaultsTestOnly()) + GTEST_SKIP() << "Memory tagging is not supported"; + + BufferSize = getPageSizeCached(); + Buffer = reinterpret_cast( + map(nullptr, BufferSize, "MemtagTest", MAP_MEMTAG, &Data)); + Addr = reinterpret_cast(Buffer); + EXPECT_TRUE(isAligned(Addr, archMemoryTagGranuleSize())); + EXPECT_EQ(Addr, untagPointer(Addr)); + } + + void TearDown() override { + if (Buffer) + unmap(Buffer, BufferSize, 0, &Data); + } + + uptr BufferSize = 0; + MapPlatformData Data = {}; + u8 *Buffer = nullptr; + uptr Addr = 0; +}; + +TEST_F(MemtagTest, ArchMemoryTagGranuleSize) { + EXPECT_GT(archMemoryTagGranuleSize(), 1u); + EXPECT_TRUE(isPowerOfTwo(archMemoryTagGranuleSize())); +} + +TEST_F(MemtagTest, ExtractTag) { + uptr Tags = 0; + // Try all value for the top byte and check the tags values are in the + // expected range. + for (u64 Top = 0; Top < 0x100; ++Top) + Tags = Tags | (1u << extractTag(Addr | (Top << 56))); + EXPECT_EQ(0xffff, Tags); +} + +TEST_F(MemtagTest, AddFixedTag) { + for (uptr Tag = 0; Tag < 0x10; ++Tag) + EXPECT_EQ(Tag, extractTag(addFixedTag(Addr, Tag))); + if (SCUDO_DEBUG) { + EXPECT_DEBUG_DEATH(addFixedTag(Addr, 16), ""); + EXPECT_DEBUG_DEATH(addFixedTag(~Addr, 0), ""); + } +} + +TEST_F(MemtagTest, UntagPointer) { + uptr UnTagMask = untagPointer(~uptr(0)); + for (u64 Top = 0; Top < 0x100; ++Top) { + uptr Ptr = (Addr | (Top << 56)) & UnTagMask; + EXPECT_EQ(addFixedTag(Ptr, 0), untagPointer(Ptr)); + } +} + +TEST_F(MemtagTest, ScopedDisableMemoryTagChecks) { + u8 *P = reinterpret_cast(addFixedTag(Addr, 1)); + EXPECT_NE(P, Buffer); + + EXPECT_DEATH(*P = 20, ""); + ScopedDisableMemoryTagChecks Disable; + *P = 10; +} + +TEST_F(MemtagTest, SelectRandomTag) { + for (uptr SrcTag = 0; SrcTag < 0x10; ++SrcTag) { + uptr Ptr = addFixedTag(Addr, SrcTag); + uptr Tags = 0; + for (uptr I = 0; I < 100000; ++I) + Tags = Tags | (1u << extractTag(selectRandomTag(Ptr, 0))); + EXPECT_EQ(0xfffe, Tags); + } +} + +TEST_F(MemtagTest, SelectRandomTagWithMask) { + for (uptr j = 0; j < 32; ++j) { + for (uptr i = 0; i < 1000; ++i) + EXPECT_NE(j, extractTag(selectRandomTag(Addr, 1ull << j))); + } +} + +TEST_F(MemtagTest, SKIP_NO_DEBUG(LoadStoreTagUnaligned)) { + for (uptr P = Addr; P < Addr + 4 * archMemoryTagGranuleSize(); ++P) { + if (P % archMemoryTagGranuleSize() == 0) + continue; + EXPECT_DEBUG_DEATH(loadTag(P), ""); + EXPECT_DEBUG_DEATH(storeTag(P), ""); + } +} + +TEST_F(MemtagTest, LoadStoreTag) { + uptr Base = Addr + 0x100; + uptr Tagged = addFixedTag(Base, 7); + storeTag(Tagged); + + EXPECT_EQ(Base - archMemoryTagGranuleSize(), + loadTag(Base - archMemoryTagGranuleSize())); + EXPECT_EQ(Tagged, loadTag(Base)); + EXPECT_EQ(Base + archMemoryTagGranuleSize(), + loadTag(Base + archMemoryTagGranuleSize())); +} + +TEST_F(MemtagTest, SKIP_NO_DEBUG(StoreTagsUnaligned)) { + for (uptr P = Addr; P < Addr + 4 * archMemoryTagGranuleSize(); ++P) { + uptr Tagged = addFixedTag(P, 5); + if (Tagged % archMemoryTagGranuleSize() == 0) + continue; + EXPECT_DEBUG_DEATH(storeTags(Tagged, Tagged), ""); + } +} + +TEST_F(MemtagTest, StoreTags) { + const uptr MaxTaggedSize = 4 * archMemoryTagGranuleSize(); + for (uptr Size = 0; Size <= MaxTaggedSize; ++Size) { + uptr NoTagBegin = Addr + archMemoryTagGranuleSize(); + uptr NoTagEnd = NoTagBegin + Size; + + u8 Tag = 5; + + uptr TaggedBegin = addFixedTag(NoTagBegin, Tag); + uptr TaggedEnd = addFixedTag(NoTagEnd, Tag); + + EXPECT_EQ(roundUpTo(TaggedEnd, archMemoryTagGranuleSize()), + storeTags(TaggedBegin, TaggedEnd)); + + uptr LoadPtr = Addr; + // Untagged left granule. + EXPECT_EQ(LoadPtr, loadTag(LoadPtr)); + + for (LoadPtr += archMemoryTagGranuleSize(); LoadPtr < NoTagEnd; + LoadPtr += archMemoryTagGranuleSize()) { + EXPECT_EQ(addFixedTag(LoadPtr, 5), loadTag(LoadPtr)); + } + + // Untagged right granule. + EXPECT_EQ(LoadPtr, loadTag(LoadPtr)); + + // Reset tags without using StoreTags. + releasePagesToOS(Addr, 0, BufferSize, &Data); + } +} + +} // namespace scudo + +#endif diff --git a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h --- a/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h +++ b/compiler-rt/lib/scudo/standalone/tests/scudo_unit_test.h @@ -39,4 +39,10 @@ #define SKIP_ON_FUCHSIA(T) T #endif +#if SCUDO_DEBUG +#define SKIP_NO_DEBUG(T) T +#else +#define SKIP_NO_DEBUG(T) DISABLED_##T +#endif + extern bool UseQuarantine;