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 @@ -66,29 +66,55 @@ return getauxval(AT_HWCAP2) & HWCAP2_MTE; } -inline bool systemDetectsMemoryTagFaultsTestOnly() { #ifndef PR_GET_TAGGED_ADDR_CTRL #define PR_GET_TAGGED_ADDR_CTRL 56 #endif +#ifndef PR_SET_TAGGED_ADDR_CTRL +#define PR_SET_TAGGED_ADDR_CTRL 54 +#endif #ifndef PR_MTE_TCF_SHIFT #define PR_MTE_TCF_SHIFT 1 #endif #ifndef PR_MTE_TCF_NONE #define PR_MTE_TCF_NONE (0UL << PR_MTE_TCF_SHIFT) #endif +#ifndef PR_MTE_TCF_SYNC +#define PR_MTE_TCF_SYNC (1UL << PR_MTE_TCF_SHIFT) +#endif #ifndef PR_MTE_TCF_MASK #define PR_MTE_TCF_MASK (3UL << PR_MTE_TCF_SHIFT) #endif +#ifndef PR_MTE_TAG_SHIFT +#define PR_MTE_TAG_SHIFT 3 +#endif +#ifndef PR_TAGGED_ADDR_ENABLE +#define PR_TAGGED_ADDR_ENABLE (1UL << 0) +#endif + +inline bool systemDetectsMemoryTagFaultsTestOnly() { return (static_cast( prctl(PR_GET_TAGGED_ADDR_CTRL, 0, 0, 0, 0)) & PR_MTE_TCF_MASK) != PR_MTE_TCF_NONE; } +inline void setSystemDetectsMemoryTagFaultsTestOnly(bool Enabled) { + prctl(PR_SET_TAGGED_ADDR_CTRL, + PR_TAGGED_ADDR_ENABLE | (Enabled ? PR_MTE_TCF_SYNC : PR_MTE_TCF_NONE) | + (0xfffe << PR_MTE_TAG_SHIFT), + 0, 0, 0); +} + #else // !SCUDO_LINUX inline bool systemSupportsMemoryTagging() { return false; } -inline bool systemDetectsMemoryTagFaultsTestOnly() { return false; } +inline bool systemDetectsMemoryTagFaultsTestOnly() { + UNREACHABLE("memory tagging not supported"); +} + +inline void setSystemDetectsMemoryTagFaultsTestOnly(bool Enabled) { + UNREACHABLE("memory tagging not supported"); +} #endif // SCUDO_LINUX @@ -148,7 +174,7 @@ inline uptr addFixedTag(uptr Ptr, uptr Tag) { DCHECK_LT(Tag, 16); - return Ptr | (Tag << 56); + return untagPointer(Ptr) | (Tag << 56); } inline uptr storeTags(uptr Begin, uptr End) { @@ -251,6 +277,10 @@ UNREACHABLE("memory tagging not supported"); } +inline void setSystemDetectsMemoryTagFaultsTestOnly(bool Enabled) { + UNREACHABLE("memory tagging not supported"); +} + inline bool disableMemoryTagChecksTestOnly() { UNREACHABLE("memory tagging not supported"); } 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,191 @@ +//===-- 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(setSystemDetectsMemoryTagFaultsTestOnly(false), "not supported"); + EXPECT_DEATH(disableMemoryTagChecksTestOnly(), "not supported"); + EXPECT_DEATH(enableMemoryTagChecksTestOnly(), "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()) + GTEST_SKIP() << "Memory tagging is not supported"; + + SavedDetectFaults = systemDetectsMemoryTagFaultsTestOnly(); + setSystemDetectsMemoryTagFaultsTestOnly(true); + + Buffer = reinterpret_cast( + map(nullptr, 1u << 20, "MemtagTest", MAP_MEMTAG, &Data)); + Addr = reinterpret_cast(Buffer); + EXPECT_TRUE(isAligned(Addr, archMemoryTagGranuleSize())); + } + + void TearDown() override { + if (Buffer) + unmap(Buffer, getPageSizeCached(), 0, &Data); + setSystemDetectsMemoryTagFaultsTestOnly(SavedDetectFaults); + } + + bool SavedDetectFaults = false; + uptr Addr = 0; + u8 *Buffer = nullptr; + MapPlatformData Data = {}; +}; + +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 (uptr 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), ""); +} + +TEST_F(MemtagTest, UntagPointer) { + for (uptr Top = 0; Top < 0x100; ++Top) { + uptr Ptr = Addr | (Top << 56); + EXPECT_EQ(addFixedTag(Ptr, 0), untagPointer(Ptr)); + } +} + +TEST_F(MemtagTest, SetSystemDetectsMemoryTagFaultsTestOnly) { + u8 *P = reinterpret_cast(selectRandomTag(Addr, 0)); + EXPECT_NE(P, Buffer); + + setSystemDetectsMemoryTagFaultsTestOnly(false); + if (!systemDetectsMemoryTagFaultsTestOnly()) { + // Can't try this if setSystemDetectsMemoryTagFaultsTestOnly failed. + *P = 10; + } + + setSystemDetectsMemoryTagFaultsTestOnly(true); + EXPECT_DEATH(*P = 20, ""); +} + +TEST_F(MemtagTest, MemoryTagChecksTestOnly) { + u8 *P = reinterpret_cast(selectRandomTag(Addr, 0)); + EXPECT_NE(P, Buffer); + + if (disableMemoryTagChecksTestOnly()) { + // Can't try this if disableMemoryTagChecksTestOnly failed. + *P = 10; + } + + enableMemoryTagChecksTestOnly(); + EXPECT_DEATH(*P = 20, ""); +} + +TEST_F(MemtagTest, SelectRandomTag) { + std::set tags; + for (uptr i = 0; i < 100000; ++i) + tags.insert(extractTag(selectRandomTag(Addr, 0))); + EXPECT_EQ(15u, tags.size()); +} + +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) { + uptr P = Addr + 0x100; + for (uptr Size = 0; Size < 4 * archMemoryTagGranuleSize(); + ++Size, P += 0x100) { + uptr Tagged = addFixedTag(P, 5); + uptr TaggedEnd = Tagged + Size; + + EXPECT_EQ(roundUpTo(TaggedEnd, archMemoryTagGranuleSize()), + storeTags(Tagged, TaggedEnd)); + + uptr LoadPtr = P - archMemoryTagGranuleSize(); + EXPECT_EQ(LoadPtr, loadTag(LoadPtr)); + for (LoadPtr += archMemoryTagGranuleSize(); LoadPtr < P + Size; + LoadPtr += archMemoryTagGranuleSize()) { + EXPECT_EQ(addFixedTag(LoadPtr, 5), loadTag(LoadPtr)); + } + EXPECT_EQ(LoadPtr, loadTag(LoadPtr)); + } +} + +} // 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;