1//===-- memtag_test.cpp -----------------------------------------*- C++ -*-===//
2//
3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4// See https://llvm.org/LICENSE.txt for license information.
5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6//
7//===----------------------------------------------------------------------===//
8
9#include "common.h"
10#include "mem_map.h"
11#include "memtag.h"
12#include "platform.h"
13#include "tests/scudo_unit_test.h"
14
15extern "C" void __hwasan_init() __attribute__((weak));
16
17#if SCUDO_LINUX
18namespace scudo {
19
20TEST(MemtagBasicDeathTest, Unsupported) {
21 if (archSupportsMemoryTagging())
22 GTEST_SKIP();
23 // Skip when running with HWASan.
24 if (&__hwasan_init != 0)
25 GTEST_SKIP();
26
27 EXPECT_DEATH(archMemoryTagGranuleSize(), "not supported");
28 EXPECT_DEATH(untagPointer((uptr)0), "not supported");
29 EXPECT_DEATH(extractTag((uptr)0), "not supported");
30
31 EXPECT_DEATH(systemSupportsMemoryTagging(), "not supported");
32 EXPECT_DEATH(systemDetectsMemoryTagFaultsTestOnly(), "not supported");
33 EXPECT_DEATH(enableSystemMemoryTaggingTestOnly(), "not supported");
34
35 EXPECT_DEATH(selectRandomTag((uptr)0, 0), "not supported");
36 EXPECT_DEATH(addFixedTag((uptr)0, 1), "not supported");
37 EXPECT_DEATH(storeTags((uptr)0, (uptr)0 + sizeof(0)), "not supported");
38 EXPECT_DEATH(storeTag((uptr)0), "not supported");
39 EXPECT_DEATH(loadTag((uptr)0), "not supported");
40
41 EXPECT_DEATH(setRandomTag(nullptr, 64, 0, nullptr, nullptr), "not supported");
42 EXPECT_DEATH(untagPointer(nullptr), "not supported");
43 EXPECT_DEATH(loadTag(nullptr), "not supported");
44 EXPECT_DEATH(addFixedTag(nullptr, 0), "not supported");
45}
46
47class MemtagTest : public Test {
48protected:
49 void SetUp() override {
50 if (!archSupportsMemoryTagging() || !systemDetectsMemoryTagFaultsTestOnly())
51 GTEST_SKIP() << "Memory tagging is not supported";
52
53 BufferSize = getPageSizeCached();
54 ASSERT_FALSE(MemMap.isAllocated());
55 ASSERT_TRUE(MemMap.map(/*Addr=*/0U, Size: BufferSize, Name: "MemtagTest", MAP_MEMTAG));
56 ASSERT_NE(MemMap.getBase(), 0U);
57 Addr = MemMap.getBase();
58 Buffer = reinterpret_cast<u8 *>(Addr);
59 EXPECT_TRUE(isAligned(X: Addr, Alignment: archMemoryTagGranuleSize()));
60 EXPECT_EQ(Addr, untagPointer(Ptr: Addr));
61 }
62
63 void TearDown() override {
64 if (Buffer) {
65 ASSERT_TRUE(MemMap.isAllocated());
66 MemMap.unmap(Addr: MemMap.getBase(), Size: MemMap.getCapacity());
67 }
68 }
69
70 uptr BufferSize = 0;
71 scudo::MemMapT MemMap = {};
72 u8 *Buffer = nullptr;
73 uptr Addr = 0;
74};
75
76using MemtagDeathTest = MemtagTest;
77
78TEST_F(MemtagTest, ArchMemoryTagGranuleSize) {
79 EXPECT_GT(archMemoryTagGranuleSize(), 1u);
80 EXPECT_TRUE(isPowerOfTwo(X: archMemoryTagGranuleSize()));
81}
82
83TEST_F(MemtagTest, ExtractTag) {
84// The test is already skipped on anything other than 64 bit. But
85// compiling on 32 bit leads to warnings/errors, so skip compiling the test.
86#if defined(__LP64__)
87 uptr Tags = 0;
88 // Try all value for the top byte and check the tags values are in the
89 // expected range.
90 for (u64 Top = 0; Top < 0x100; ++Top)
91 Tags = Tags | (1u << extractTag(Addr | (Top << 56)));
92 EXPECT_EQ(0xffffull, Tags);
93#endif
94}
95
96TEST_F(MemtagDeathTest, AddFixedTag) {
97 for (uptr Tag = 0; Tag < 0x10; ++Tag)
98 EXPECT_EQ(Tag, extractTag(addFixedTag(Addr, Tag)));
99 if (SCUDO_DEBUG) {
100 EXPECT_DEATH(addFixedTag(Addr, 16), "");
101 EXPECT_DEATH(addFixedTag(~Addr, 0), "");
102 }
103}
104
105TEST_F(MemtagTest, UntagPointer) {
106 uptr UnTagMask = untagPointer(Ptr: ~uptr(0));
107 for (u64 Top = 0; Top < 0x100; ++Top) {
108 uptr Ptr = (Addr | (Top << 56)) & UnTagMask;
109 EXPECT_EQ(addFixedTag(Ptr, Tag: 0), untagPointer(Ptr));
110 }
111}
112
113TEST_F(MemtagDeathTest, ScopedDisableMemoryTagChecks) {
114 u8 *P = reinterpret_cast<u8 *>(addFixedTag(Addr, 1));
115 EXPECT_NE(P, Buffer);
116
117 EXPECT_DEATH(*P = 20, "");
118 ScopedDisableMemoryTagChecks Disable;
119 *P = 10;
120}
121
122TEST_F(MemtagTest, SelectRandomTag) {
123 for (uptr SrcTag = 0; SrcTag < 0x10; ++SrcTag) {
124 uptr Ptr = addFixedTag(Addr, SrcTag);
125 uptr Tags = 0;
126 for (uptr I = 0; I < 100000; ++I)
127 Tags = Tags | (1u << extractTag(Ptr: selectRandomTag(Ptr, ExcludeMask: 0)));
128 // std::popcnt is C++20
129 int PopCnt = 0;
130 while (Tags) {
131 PopCnt += Tags & 1;
132 Tags >>= 1;
133 }
134 // Random tags are not always very random, and this test is not about PRNG
135 // quality. Anything above half would be satisfactory.
136 EXPECT_GE(PopCnt, 8);
137 }
138}
139
140TEST_F(MemtagTest, SelectRandomTagWithMask) {
141// The test is already skipped on anything other than 64 bit. But
142// compiling on 32 bit leads to warnings/errors, so skip compiling the test.
143#if defined(__LP64__)
144 for (uptr j = 0; j < 32; ++j) {
145 for (uptr i = 0; i < 1000; ++i)
146 EXPECT_NE(j, extractTag(selectRandomTag(Addr, 1ull << j)));
147 }
148#endif
149}
150
151TEST_F(MemtagDeathTest, SKIP_NO_DEBUG(LoadStoreTagUnaligned)) {
152 for (uptr P = Addr; P < Addr + 4 * archMemoryTagGranuleSize(); ++P) {
153 if (P % archMemoryTagGranuleSize() == 0)
154 continue;
155 EXPECT_DEATH(loadTag(P), "");
156 EXPECT_DEATH(storeTag(P), "");
157 }
158}
159
160TEST_F(MemtagTest, LoadStoreTag) {
161 uptr Base = Addr + 0x100;
162 uptr Tagged = addFixedTag(Ptr: Base, Tag: 7);
163 storeTag(Ptr: Tagged);
164
165 EXPECT_EQ(Base - archMemoryTagGranuleSize(),
166 loadTag(Ptr: Base - archMemoryTagGranuleSize()));
167 EXPECT_EQ(Tagged, loadTag(Ptr: Base));
168 EXPECT_EQ(Base + archMemoryTagGranuleSize(),
169 loadTag(Ptr: Base + archMemoryTagGranuleSize()));
170}
171
172TEST_F(MemtagDeathTest, SKIP_NO_DEBUG(StoreTagsUnaligned)) {
173 for (uptr P = Addr; P < Addr + 4 * archMemoryTagGranuleSize(); ++P) {
174 uptr Tagged = addFixedTag(P, 5);
175 if (Tagged % archMemoryTagGranuleSize() == 0)
176 continue;
177 EXPECT_DEATH(storeTags(Tagged, Tagged), "");
178 }
179}
180
181TEST_F(MemtagTest, StoreTags) {
182// The test is already skipped on anything other than 64 bit. But
183// compiling on 32 bit leads to warnings/errors, so skip compiling the test.
184#if defined(__LP64__)
185 const uptr MaxTaggedSize = 4 * archMemoryTagGranuleSize();
186 for (uptr Size = 0; Size <= MaxTaggedSize; ++Size) {
187 uptr NoTagBegin = Addr + archMemoryTagGranuleSize();
188 uptr NoTagEnd = NoTagBegin + Size;
189
190 u8 Tag = 5;
191
192 uptr TaggedBegin = addFixedTag(Ptr: NoTagBegin, Tag);
193 uptr TaggedEnd = addFixedTag(Ptr: NoTagEnd, Tag);
194
195 EXPECT_EQ(roundUp(X: TaggedEnd, Boundary: archMemoryTagGranuleSize()),
196 storeTags(Begin: TaggedBegin, End: TaggedEnd));
197
198 uptr LoadPtr = Addr;
199 // Untagged left granule.
200 EXPECT_EQ(LoadPtr, loadTag(Ptr: LoadPtr));
201
202 for (LoadPtr += archMemoryTagGranuleSize(); LoadPtr < NoTagEnd;
203 LoadPtr += archMemoryTagGranuleSize()) {
204 EXPECT_EQ(addFixedTag(Ptr: LoadPtr, Tag: 5), loadTag(Ptr: LoadPtr));
205 }
206
207 // Untagged right granule.
208 EXPECT_EQ(LoadPtr, loadTag(Ptr: LoadPtr));
209
210 // Reset tags without using StoreTags.
211 MemMap.releasePagesToOS(Addr, BufferSize);
212 }
213#endif
214}
215
216} // namespace scudo
217
218#endif
219

source code of compiler-rt/lib/scudo/standalone/tests/memtag_test.cpp