Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 | #include <darwintest.h> #include "mte_testing.h" #if MALLOC_MTE_TESTING_SUPPORTED #include <arm_acle.h> #include <mach/mach.h> // We need to pass CONFIG_MTE=1 when building this test, as otherwise the // instrumentation file we will include will not have the code we mean to // test. #ifndef CONFIG_MTE #error "CONFIG_MTE is not defined" #elif !CONFIG_MTE #error "CONFIG_MTE needs to be true" #endif // This file implements unit tests for the implementation of the instrumentation // we have in instrumentation.c; therefore, we directly include the // implementation file here. #include <../src/instrumentation.c> #define T_CHECK_TAGS(__ptr, __size, __msg) do { \ T_QUIET; T_ASSERT_TRUE(((uintptr_t)(__ptr) & 0xf) == 0, \ "Should be 16-bytes aligned"); \ T_QUIET; T_ASSERT_TRUE((__size & 0xf) == 0, "Should be multiple of 16"); \ for (size_t __s = 0; __s < __size; __s += 16) { \ uint8_t *__p = &((uint8_t *)__ptr)[__s]; \ uint8_t *__ldg = __arm_mte_get_tag(__p); \ T_QUIET; T_ASSERT_EQ_PTR(__p, __ldg, \ __msg " (%p[%zu]: %p : %p)", __ptr, __s, __p, __ldg); \ } \ } while (0) T_GLOBAL_META(T_META_RUN_CONCURRENTLY(true), T_META_TAG_VM_NOT_PREFERRED, T_META_TAG_XZONE_ONLY); // Allocate `page_cnt` contiguous taggable pages. // Asserts that all operations succeed. static void allocate_mte_pages(size_t page_cnt, vm_address_t *address) { T_QUIET; T_ASSERT_GT(page_cnt, 0ul, "page_cnt must be > 0"); vm_address_t addr = 0; kern_return_t kr = vm_allocate(mach_task_self(), &addr, PAGE_SIZE * page_cnt, VM_FLAGS_ANYWHERE | VM_FLAGS_MTE); T_QUIET; T_ASSERT_MACH_SUCCESS(kr, "allocate tagged memory"); T_QUIET; T_ASSERT_NE(addr, 0ul, "vm_allocate returned NULL"); *address = addr; } // Choose a random 16-byte granule within the given pages, guaranteeing that // there are at least `room` bytes between the start of the granule and the // end of the last page. static uint8_t * get_random_granule(vm_address_t page_addr, size_t pages_cnt, size_t room) { uint32_t idx = arc4random_uniform(PAGE_SIZE * pages_cnt - room); vm_address_t addr = (page_addr + idx) & ~0xf; return (uint8_t *)addr; } T_DECL(mte_unit_memtag_set_tag, "Check that memtag_set_tag works for sizes between 1 and 32768") { T_SKIP_REQUIRES_SEC_TRANSITION(); vm_address_t page_addr = 0; const size_t max_sz = 32 << 10; const size_t pages_cnt = max_sz / PAGE_SIZE ?: 1; allocate_mte_pages(pages_cnt, &page_addr); size_t step = 1; for (size_t sz = 1; sz <= max_sz; sz += step) { size_t sz = memtag_p2roundup(sz, 16); uint8_t *granule = get_random_granule(page_addr, pages_cnt, sz); uint8_t *tag_addr = __arm_mte_create_random_tag(granule, 0x0001); T_CHECK_TAGS(granule, sz, "Initial tag should be zero"); memtag_set_tag(tag_addr, sz); T_CHECK_TAGS(tag_addr, sz, "Tag should be set correctly"); memtag_set_tag(granule, sz); T_CHECK_TAGS(granule, sz, "Tag should be back to zero"); if (sz == 1024) { step = 256; } } T_PASS("memtag_set_tag"); } T_DECL(mte_unit_memtag_exclude_tag, "Check that _memtag_exclude_tag works") { T_SKIP_REQUIRES_SEC_TRANSITION(); uint8_t *page_addr; const uint64_t mask = 0x0001; allocate_mte_pages(1, (vm_address_t *)&page_addr); // Choose a non-canonical tag. uint8_t *tagged_addr = __arm_mte_create_random_tag(page_addr, mask); // Set the tag for the granule. __arm_mte_set_tag(tagged_addr); // Test _memtag_exclude_tag with a pointer with the right logical tag. uint64_t m = _memtag_exclude_tag(tagged_addr, mask); T_QUIET; T_ASSERT_EQ(__builtin_popcount(m), 2, "Two tags should be excluded (tagged pointer)"); // Test _memtag_exclude_tag with a pointer without the right logical tag. m = _memtag_exclude_tag(page_addr, mask); T_QUIET; T_ASSERT_EQ(__builtin_popcount(m), 2, "Two tags should be excluded (canonical pointer)"); T_PASS("_memtag_exclude_tag"); } typedef enum { block_placement_in_page, block_placement_end, block_placement_beginning, block_placement_across_pages, } block_placement_t; #define kBlockTagLeft 0xa #define kBlockTagCenter 0x1 #define kBlockTagRight 0xb static void _check_memtag_assign_tag_loop(uint8_t *block_ptr, size_t sz, block_placement_t placement) { for (size_t i = 0; i < 1024; i++) { uint8_t *new_g = memtag_assign_tag(block_ptr, sz); uint8_t new_tag = PTR_EXTRACT_TAG(new_g); T_QUIET; T_ASSERT_NE(new_tag, 0, "Should not be the canonical tag"); T_QUIET; T_ASSERT_NE(new_tag, kBlockTagCenter, "Should not be equal to the current tag"); switch (placement) { case block_placement_in_page: case block_placement_across_pages: // We should be able to exclude both the left and right tags. // This applies also when the block spans across pages. T_QUIET; T_ASSERT_NE(new_tag, kBlockTagLeft, "Should not be equal to the adjacent tag (left)"); T_QUIET; T_ASSERT_NE(new_tag, kBlockTagRight, "Should not be equal to the adjacent tag (right)"); break; case block_placement_end: // We are only able to exclude the left neighbour's tag. T_QUIET; T_ASSERT_NE(new_tag, kBlockTagLeft, "Should not be equal to the adjacent tag (left)"); break; case block_placement_beginning: // We are only able to exclude the right neighbour's tag. T_QUIET; T_ASSERT_NE(new_tag, kBlockTagRight, "Should not be equal to the adjacent tag (right)"); break; } } } static void _unit_test_memtag_assign_tag(block_placement_t placement) { uint8_t *page_addr; const uint64_t mask = 0x0001; const size_t num_pages = (placement == block_placement_in_page) ? 1 : 2; allocate_mte_pages(num_pages, (vm_address_t *)&page_addr); for (size_t sz = 16; sz < PAGE_SIZE / 4; sz *= 2) { // Choose 3 blocks: [l][g][r] uint8_t *l = NULL; uint8_t *g = NULL; uint8_t *r = NULL; switch (placement) { case block_placement_in_page: // Get 3 blocks within the page l = get_random_granule((vm_address_t)page_addr, num_pages, 3 * sz); g = l + sz; r = g + sz; T_QUIET; T_ASSERT_TRUE((r + sz) <= (page_addr + PAGE_SIZE), "Right granule within the first page"); break; case block_placement_end: // The center block should be at the end of the first page g = page_addr + PAGE_SIZE - sz; l = g - sz; r = g + sz; break; case block_placement_beginning: // The center block should be at the beginning of the second page g = page_addr + PAGE_SIZE; l = g - sz; r = g + sz; break; case block_placement_across_pages: // The center block should span across the two pages g = page_addr + PAGE_SIZE - (sz / 2); l = g - sz; r = g + sz; break; } T_QUIET; T_ASSERT_TRUE(l >= page_addr, "Left is within the first page"); // Tag them with fixed tags [a][1][b] uint8_t *tg = PTR_SET_TAG(g, kBlockTagCenter); uint8_t *tl = PTR_SET_TAG(l, kBlockTagLeft); uint8_t *tr = PTR_SET_TAG(r, kBlockTagRight); for (size_t i = 0; i < sz; i += 16) { __arm_mte_set_tag(tg + i); __arm_mte_set_tag(tl + i); __arm_mte_set_tag(tr + i); } // Verify the tags on the first granule of each block. T_QUIET; T_ASSERT_EQ_PTR(tg, __arm_mte_get_tag(g), "Tagged (g)"); T_QUIET; T_ASSERT_EQ_PTR(tl, __arm_mte_get_tag(l), "Tagged (l)"); T_QUIET; T_ASSERT_EQ_PTR(tr, __arm_mte_get_tag(r), "Tagged (r)"); // Assign a new tag to g, using the tagged pointer _check_memtag_assign_tag_loop(tg, sz, placement); // Assign a new tag to g, using the canonical pointer _check_memtag_assign_tag_loop(g, sz, placement); } } T_DECL(mte_unit_memtag_assign_tag_in_page, "Check that memtag_assign_tag (block_placement_in_page)") { T_SKIP_REQUIRES_SEC_TRANSITION(); _unit_test_memtag_assign_tag(block_placement_in_page); T_PASS("memtag_assign_tag (block_placement_in_page)"); } T_DECL(mte_unit_memtag_assign_tag_end, "Check that memtag_assign_tag (block_placement_end)") { T_SKIP_REQUIRES_SEC_TRANSITION(); _unit_test_memtag_assign_tag(block_placement_end); T_PASS("memtag_assign_tag (block_placement_end)"); } T_DECL(mte_unit_memtag_assign_tag_beginning, "Check that memtag_assign_tag (block_placement_beginning)") { T_SKIP_REQUIRES_SEC_TRANSITION(); _unit_test_memtag_assign_tag(block_placement_beginning); T_PASS("memtag_assign_tag (block_placement_beginning)"); } T_DECL(mte_unit_memtag_init_chunk, "Check that memtag_init_chunk works") { T_SKIP_REQUIRES_SEC_TRANSITION(); uint8_t *page_addr = NULL; allocate_mte_pages(1, (vm_address_t *)&page_addr); for (size_t sz = 16; sz < PAGE_SIZE / 4; sz += 16) { const size_t remainder = PAGE_SIZE % sz; // Reset the tags on the page for (size_t i = 0; i < PAGE_SIZE; i += 16) { __arm_mte_set_tag(&page_addr[i]); } memtag_init_chunk(page_addr, PAGE_SIZE, sz); uint8_t prev_tag = 0; for (size_t i = 0; i < PAGE_SIZE - remainder; i += sz) { uint8_t *p = &page_addr[i]; uint8_t *block_ldg = __arm_mte_get_tag(p); uint16_t cur_tag = PTR_EXTRACT_TAG(block_ldg); T_QUIET; T_ASSERT_NE(cur_tag, prev_tag, "Adjacent blocks should have different tags"); T_QUIET; T_ASSERT_NE(cur_tag, 0, "Block should have a non-canonical tag"); // Check that all the granules of the block have the same tag for (size_t j = 0; j < sz; j += 16) { uint8_t *g = &page_addr[i + j]; uint8_t *granule_ldg = __arm_mte_get_tag(g); uint16_t granule_tag = PTR_EXTRACT_TAG(granule_ldg); T_QUIET; T_ASSERT_EQ(cur_tag, granule_tag, "Granule should have a consistent tag", sz, i); } } // Check that the remainder is canonically tagged for (size_t i = PAGE_SIZE - remainder; i < PAGE_SIZE; i += 16) { uint8_t *p = &page_addr[i]; uint8_t *ldg = __arm_mte_get_tag(p); T_QUIET; T_ASSERT_EQ_PTR(p, ldg, "Remainder (sz=%zu, %zu) should have canonical tag", sz, remainder); } } T_PASS("memtag_init_chunk"); } #else // MALLOC_MTE_TESTING_SUPPORTED T_DECL(mte_unit_unsupported_target, "Skip testing on unsupported targets", T_META_TAG_VM_PREFERRED, T_META_TAG_NO_ALLOCATOR_OVERRIDE) { T_SKIP("MTE unit tests are only implemented for arm64 targets"); } #endif // MALLOC_MTE_TESTING_SUPPORTED |