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 | /* * Copyright (c) 2025 Apple Inc. All rights reserved. * * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ * * This file contains Original Code and/or Modifications of Original Code * as defined in and that are subject to the Apple Public Source License * Version 2.0 (the 'License'). You may not use this file except in * compliance with the License. The rights granted to you under the License * may not be used to create, or enable the creation or redistribution of, * unlawful or unlicensed copies of an Apple operating system, or to * circumvent, violate, or enable the circumvention or violation of, any * terms of an Apple operating system software license agreement. * * Please obtain a copy of the License at * http://www.opensource.apple.com/apsl/ and read it before using this file. * * The Original Code and all software distributed under the License are * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. * Please see the License for the specific language governing rights and * limitations under the License. * * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ */ #include <arm64/sptm/pmap/pmap.h> #include <darwintest.h> #include "ecc_utils.h" #include "mocks/osfmk/mock_ecc.h" #include "mocks/osfmk/unit_test_utils.h" #include "mocks/fake_libsptm.h" #define UT_MODULE osfmk T_GLOBAL_META( T_META_NAMESPACE("xnu.unit.ecc_test_pmap"), T_META_RADAR_COMPONENT_NAME("xnu"), T_META_RADAR_COMPONENT_VERSION("arm"), T_META_OWNER("mnitenko") ); T_DECL(test_pmap_type_is_ecc_retireable_returns_true, "Test pmap_type_is_ecc_retireable returns true for retirable pages") { bool result = pmap_type_is_ecc_retireable(XNU_DEFAULT); T_ASSERT_TRUE(result, "pmap_type_is_ecc_retireable should return true for frame type %u", XNU_DEFAULT); } T_DECL(test_pmap_type_is_ecc_retireable_returns_false, "Test pmap_type_is_ecc_retireable returns false for non-retirable pages") { /* Array of frame types that should NOT be retirable. */ const sptm_frame_type_t non_retirable_types[] = { SPTM_DEFAULT, SPTM_CODE, }; const size_t size = sizeof(non_retirable_types) / sizeof(non_retirable_types[0]); for (size_t i = 0; i < size; i++) { bool result = pmap_type_is_ecc_retireable(non_retirable_types[i]); T_ASSERT_FALSE(result, "pmap_type_is_ecc_retireable should return false for a non-retirable frame type %u", non_retirable_types[i]); } } T_DECL(test_pmap_type_is_ecc_retireable_panics_on_sptm_error, "Test pmap_type_is_ecc_retireable panics if (mocked) sptm_type_is_ecc_retireable returns an error") { sptm_type_is_ecc_retireable_set_ret(LIBSPTM_NOT_INITTED); T_ASSERT_PANIC_CONTAINS(pmap_type_is_ecc_retireable(XNU_DEFAULT), "pmap_type_is_ecc_retireable: failed for type", "Panic if sptm_type_is_ecc_retireable returns an error"); } /* To retire a page the system has to be ECC capable. */ T_MOCK_SET_PERM_RETVAL(vm_ecc_capable, bool, true); static unsigned ml_page_clear_poison_calls = 0; T_MOCK_SET_PERM_FUNC(void, ml_page_clear_poison, (vm_address_t va)) { ml_page_clear_poison_calls++; return; } extern boolean_t pmap_initialized; __attribute__((constructor)) void pmap_test_init(void) { setup_mem(); /* * Should be able to hold the number of VM pages that span all of * kernel-managed memory. */ pv_head_table = malloc(atop(mem_size) * sizeof(uintptr_t)); pmap_initialized = true; } __attribute__((destructor)) void pmap_test_deinit(void) { pmap_initialized = false; free(pv_head_table); } /** * Set up page as retired. * * @param addr Physical address to mark as retired. */ static void pmap_pre_retire(const pmap_paddr_t addr) { const unsigned int pai = pa_index(addr); locked_pvh_t locked_pvh = pvh_lock(pai); pvh_set_flags(&locked_pvh, pvh_get_flags(locked_pvh.pvh) | PVH_FLAG_RETIRED); pvh_unlock(&locked_pvh); } T_DECL(retire_ecc_not_capable, "Retiring a page when ECC is not capable") { T_SETUPBEGIN; const pmap_paddr_t addr = gPhysBase + 900 * PAGE_SIZE; sptm_get_paddr_type_set_type(XNU_DEFAULT); pmap_pre_retire(addr); T_SETUPEND; /* Mock vm_ecc_capable to return false. */ T_MOCK_SET_RETVAL(vm_ecc_capable, bool, false); T_ASSERT_PANIC_CONTAINS(pmap_retire_page(atop(addr)), "Assertion failed", "Should panic when ECC is not capable."); T_ASSERT_EQ(ml_page_clear_poison_calls, 0, "Did not try to clear poison."); T_ASSERT_NE(sptm_retype_get_type(), SPTM_UNUSED, "Did not retype the page."); } T_DECL(retire_non_managed, "Retiring non-kernel managed pages") { T_SETUPBEGIN; const pmap_paddr_t addr_out_of_dram = 10; const pmap_paddr_t addr_dram_start = gDramBase; T_SETUPEND; T_ASSERT_PANIC_CONTAINS(pmap_retire_page(atop(addr_out_of_dram)), "attempt to retire non-kernel managed PA", "Can't retire unmanaged PAs (out of DRAM)."); T_ASSERT_NE(sptm_retype_get_type(), SPTM_UNUSED, "Did not retype the page."); T_ASSERT_PANIC_CONTAINS(pmap_retire_page(atop(addr_dram_start)), "attempt to retire non-kernel managed PA", "Can't retire unmanaged PAs (start of DRAM)."); T_ASSERT_NE(sptm_retype_get_type(), SPTM_UNUSED, "Did not retype the page."); T_ASSERT_EQ(ml_page_clear_poison_calls, 0, "Did not try to clear poison for non-managed pages."); } T_DECL(retire_without_retired_flag, "Retiring a page that lacks PVH_FLAG_RETIRED flag") { T_SETUPBEGIN; const pmap_paddr_t addr = gPhysBase + 200 * PAGE_SIZE; sptm_get_paddr_type_set_type(XNU_DEFAULT); T_SETUPEND; T_ASSERT_PANIC_CONTAINS(pmap_retire_page(atop(addr)), "that is not FLAG_RETIRED", "Should panic when trying to retire page without PVH_FLAG_RETIRED flag."); T_ASSERT_EQ(ml_page_clear_poison_calls, 0, "Did not try to clear poison."); T_ASSERT_NE(sptm_retype_get_type(), SPTM_UNUSED, "Did not retype the page."); } T_DECL(retire_with_mappings, "Retiring a page with active mappings") { T_SETUPBEGIN; const pmap_paddr_t addr = gPhysBase + 300 * PAGE_SIZE; sptm_get_paddr_type_set_type(XNU_DEFAULT); pmap_pre_retire(addr); const unsigned int pai = pa_index(addr); locked_pvh_t locked_pvh = pvh_lock(pai); /* Set PVH_TYPE_PTEP to simulate a mapping. */ pvh_update_head(&locked_pvh, &locked_pvh, PVH_TYPE_PTEP); pvh_unlock(&locked_pvh); T_SETUPEND; T_ASSERT_PANIC_CONTAINS(pmap_retire_page(atop(addr)), "with mappings", "Should panic when trying to retire page that has mappings."); } T_DECL(retire_non_retirable, "Retiring a non-retirable page type") { T_SETUPBEGIN; const pmap_paddr_t addr = gPhysBase + 400 * PAGE_SIZE; /* Set the page type to be something non-retirable -- SPTM_CODE. */ sptm_get_paddr_type_set_type(SPTM_CODE); pmap_pre_retire(addr); T_SETUPEND; T_ASSERT_PANIC_CONTAINS(pmap_retire_page(atop(addr)), "attempt to retire a non-retirable", "Should panic when trying to retire a non-retirable page."); T_ASSERT_EQ(ml_page_clear_poison_calls, 0, "Did not try to clear poison."); T_ASSERT_NE(sptm_retype_get_type(), SPTM_UNUSED, "Did not retype the page."); } T_DECL(retire_limits, "Retire first and last managed pages") { T_SETUPBEGIN; const unsigned expected_calls = 2; sptm_get_paddr_type_set_type(XNU_DEFAULT); /* Test with a page at the very beginning of managed memory. */ const pmap_paddr_t first_page = gPhysBase; pmap_pre_retire(first_page); /* Test with the last page of managed memory. */ const pmap_paddr_t last_page = gPhysBase + (gPhysSize - PAGE_SIZE); pmap_pre_retire(last_page); T_SETUPEND; pmap_retire_page(atop(first_page)); T_ASSERT_TRUE(pmap_is_page_retired(atop(first_page)), "First page of managed memory should be retired."); T_ASSERT_EQ(sptm_retype_get_type(), SPTM_UNUSED, "Retyped the poisoned page."); pmap_retire_page(atop(last_page)); T_ASSERT_TRUE(pmap_is_page_retired(atop(last_page)), "Page at the end of managed memory should be retired."); T_ASSERT_EQ(sptm_retype_get_type(), SPTM_UNUSED, "Retyped the poisoned page."); T_ASSERT_EQ(ml_page_clear_poison_calls, expected_calls, "Cleared poison for retired pages."); } T_DECL(is_page_retired_non_managed, "Test if non-kernel managed pages are retired") { const pmap_paddr_t addr_dram_start = gDramBase; const pmap_paddr_t addr_out_of_dram = 10; T_ASSERT_FALSE(pmap_is_page_retired(atop(addr_dram_start)), "Non-kernel managed pages should not be considered retired."); T_ASSERT_FALSE(pmap_is_page_retired(atop(addr_out_of_dram)), "Out of DRAM pages should not be considered retired."); } T_DECL(is_page_retired_before_init, "Test if pages are retired before pmap initialization") { T_SETUPBEGIN; const pmap_paddr_t addr = gPhysBase + 600 * PAGE_SIZE; pmap_pre_retire(addr); /* Mock pmap not initialized. */ pmap_initialized = false; T_SETUPEND; T_ASSERT_FALSE(pmap_is_page_retired(atop(addr)), "Pages should not be considered retired before pmap initialization (even if the page is somehow retired)."); } T_DECL(is_page_retired_flag_set, "Test if pages are retired with PVH_FLAG_RETIRED set") { T_SETUPBEGIN; const pmap_paddr_t addr = gPhysBase + 700 * PAGE_SIZE; T_SETUPEND; T_ASSERT_FALSE(pmap_is_page_retired(atop(addr)), "Page should not be retired initially."); pmap_pre_retire(addr); T_ASSERT_TRUE(pmap_is_page_retired(atop(addr)), "Page with PVH_FLAG_RETIRED should be reported as retired."); } |