This is xnu-12377.1.9. See this file in:
/*
* Copyright (c) 2024 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@
*/
/*
* vm/configurator_fault_read.c
*
* Test read and write faults with many different VM states.
*/
#include <ptrauth.h>
#include "configurator/vm_configurator_tests.h"
T_GLOBAL_META(
T_META_NAMESPACE("xnu.vm.configurator"),
T_META_RADAR_COMPONENT_NAME("xnu"),
T_META_RADAR_COMPONENT_VERSION("VM"),
T_META_RUN_CONCURRENTLY(true),
T_META_ASROOT(true), /* required for vm submap sysctls */
T_META_ALL_VALID_ARCHS(true)
);
static bool
test_fault_one_checker_in_address_range(
vm_entry_checker_t *checker,
bool is_write_fault,
bool in_submap,
mach_vm_address_t checked_address,
mach_vm_size_t checked_size)
{
TEMP_CSTRING(message, "after %s 0x%llx..0x%llx%s",
is_write_fault ? "writing" : "reading",
checked_address, checked_address + checked_size,
in_submap ? " (in submap)" : "");
bool verify_reads = !is_write_fault;
bool verify_writes = is_write_fault;
bool good = verify_checker_faultability_in_address_range(checker,
message, verify_reads, verify_writes, checked_address, checked_size);
return good;
}
/*
* Call verify_checker_faultability() for one checker.
* Advance *inout_next_address_to_fault past it.
*/
static bool
test_fault_one_checker(
vm_entry_checker_t *checker,
bool is_write_fault,
bool in_submap,
mach_vm_address_t * const inout_next_address_to_fault)
{
bool good = test_fault_one_checker_in_address_range(checker,
is_write_fault, in_submap, checker->address, checker->size);
*inout_next_address_to_fault = checker_end_address(checker);
return good;
}
/*
* Call verify_checker_faultability() for one allocation checker.
* Advance *inout_next_address_to_fault past it.
*/
static bool
test_fault_one_allocation(
checker_list_t *checker_list,
vm_entry_checker_t *checker,
bool is_write_fault,
bool in_submap,
mach_vm_address_t *const inout_next_address_to_fault)
{
/* fault should not affect COW */
checker_fault_for_prot_not_cow(checker_list, checker,
is_write_fault ? VM_PROT_WRITE : VM_PROT_READ);
return test_fault_one_checker(checker, is_write_fault, in_submap, inout_next_address_to_fault);
}
/*
* Call verify_checker_faultability() for one parent map submap checker,
* or some portion thereof.
* Advance *inout_next_address_to_fault past the verified range.
*/
static bool
test_fault_one_submap(
checker_list_t *checker_list,
vm_entry_checker_t *submap_parent,
bool is_write_fault,
mach_vm_address_t *const inout_next_address_to_fault)
{
mach_vm_address_t next_address_to_fault = *inout_next_address_to_fault;
/*
* Verify up to one entry in the submap.
* The caller's loop will proceed through all entries in the submap.
*/
/* Write fault unnests up to one entry in the submap, if necessary. */
if (is_write_fault) {
mach_vm_address_t unnest_address = next_address_to_fault;
vm_entry_checker_t *unnested_checker =
checker_list_try_unnest_one_entry_in_submap(checker_list, submap_parent,
true /* unnest_readonly */, true /* all_overwritten */,
&unnest_address);
if (unnested_checker != NULL) {
/*
* Unnest occurred. Don't change *inout_next_address_to_fault
* and instead let the caller test this unnested entry's
* faultability in its next iteration.
*/
return true;
}
}
/*
* Did not unnest. Fault the nested entry (allocation or hole).
* Don't fault outside the parent map's view of the submap.
*/
/* Find the checker for the submap's entry at this address. */
checker_list_t *submap_checkers DEFER_UNSLIDE =
checker_get_and_slide_submap_checkers(submap_parent);
vm_entry_checker_t *checker =
checker_list_find_checker(submap_checkers, next_address_to_fault);
/* Compute the extent of the submap content checker that is visible to the parent map. */
mach_vm_address_t clamped_checker_address = checker->address;
mach_vm_size_t clamped_checker_size = checker->size;
clamp_address_size_to_checker(&clamped_checker_address, &clamped_checker_size, submap_parent);
assert(checker->kind == Allocation || checker->kind == Hole);
*inout_next_address_to_fault = clamped_checker_address + clamped_checker_size;
return test_fault_one_checker_in_address_range(checker, is_write_fault, true,
clamped_checker_address, clamped_checker_size);
}
static test_result_t
test_fault_common(
checker_list_t *checker_list,
mach_vm_address_t range_start,
mach_vm_size_t range_size,
bool is_write_fault, /* true for write fault, false for read fault */
bool in_submap)
{
/*
* Read or write all pages in one checker, then verify the VM state. Repeat for all checkers.
* Reading or writing in holes must provoke EXC_BAD_ACCESS (KERN_INVALID_ADDRESS).
* Reading or writing unreadable regions must provoke EXC_BAD_ACCESS (KERN_PROTECTION_FAILURE).
* Writing unwriteable regions must provoke EXC_BAD_ACCESS (KERN_PROTECTION_FAILURE).
*
* (TODO page modeling) this accesses outside [range_start, range_size)
* when the range starts or ends inside an entry
* need more precise page tracking to do better
*/
/* not FOREACH_CHECKER because submap unnesting breaks it */
mach_vm_address_t next_address_to_fault = range_start;
while (next_address_to_fault < range_start + range_size) {
vm_entry_checker_t *checker = checker_list_find_checker(checker_list, next_address_to_fault);
switch (checker->kind) {
case Allocation:
if (!test_fault_one_allocation(
checker_list, checker, is_write_fault,
in_submap, &next_address_to_fault)) {
goto failed;
}
break;
case Hole:
if (!test_fault_one_checker(
checker, is_write_fault,
in_submap, &next_address_to_fault)) {
goto failed;
}
break;
case Submap:
assert(!in_submap && "nested submaps not allowed");
if (!test_fault_one_submap(
checker_list, checker, is_write_fault,
&next_address_to_fault)) {
goto failed;
}
break;
default:
assert(0);
}
}
return TestSucceeded;
failed:
T_LOG("*** after incomplete verification of faults: all expected ***");
dump_checker_range(checker_list->entries);
T_LOG("*** after incomplete verification of faults: all actual ***");
dump_region_info_for_entries(checker_list->entries);
return TestFailed;
}
static test_result_t
test_fault_read(
checker_list_t *checker_list,
mach_vm_address_t range_start,
mach_vm_size_t range_size)
{
return test_fault_common(checker_list, range_start, range_size,
false /* is_write_fault */, false /* in_submap */);
}
static test_result_t
test_fault_write(
checker_list_t *checker_list,
mach_vm_address_t range_start,
mach_vm_size_t range_size)
{
return test_fault_common(checker_list, range_start, range_size,
true /* is_write_fault */, false /* in_submap */);
}
/*
* Resolves COW. Assumes the write operation writes to the entire object,
* so there are no shared pages remaining and the new object's shadow
* chain collapses.
*/
static void
checker_make_cow_private_with_collapsed_shadow_chain(
checker_list_t *checker_list,
vm_entry_checker_t *checker)
{
assert(checker->needs_copy);
if (checker->object->self_ref_count == 1) {
/*
* COW but not shared with anything else.
* VM resolves COW by using the same object.
*/
checker->needs_copy = false;
return;
}
/* make new object */
vm_object_checker_t *obj_checker = object_checker_clone(checker->object);
checker_list_append_object(checker_list, obj_checker);
/* change object and entry to private */
checker->needs_copy = false;
/* set new object (decreasing previous object's self_ref_count) */
checker_set_object(checker, obj_checker);
}
static test_result_t
test_fault_write_cow_1st(
checker_list_t *checker_list,
mach_vm_address_t range_start,
mach_vm_size_t range_size)
{
/*
* 1st entry is COW.
* Resolve COW because we're writing to it.
* We write to the entire entry so no shadow chain remains.
*/
checker_make_cow_private_with_collapsed_shadow_chain(
checker_list, checker_list_nth(checker_list, 0));
return test_fault_write(checker_list, range_start, range_size);
}
static test_result_t
test_fault_write_cow_2nd(
checker_list_t *checker_list,
mach_vm_address_t range_start,
mach_vm_size_t range_size)
{
/*
* 2nd entry is COW.
* Resolve COW because we're writing to it.
* We write to the entire entry so no shadow chain remains.
*/
checker_make_cow_private_with_collapsed_shadow_chain(
checker_list, checker_list_nth(checker_list, 1));
return test_fault_write(checker_list, range_start, range_size);
}
T_DECL(fault_read,
"perform read faults with various vm configurations")
{
vm_tests_t tests = {
.single_entry_1 = test_fault_read,
.single_entry_2 = test_fault_read,
.single_entry_3 = test_fault_read,
.single_entry_4 = test_fault_read,
.multiple_entries_1 = test_fault_read,
.multiple_entries_2 = test_fault_read,
.multiple_entries_3 = test_fault_read,
.multiple_entries_4 = test_fault_read,
.multiple_entries_5 = test_fault_read,
.multiple_entries_6 = test_fault_read,
.some_holes_1 = test_fault_read,
.some_holes_2 = test_fault_read,
.some_holes_3 = test_fault_read,
.some_holes_4 = test_fault_read,
.some_holes_5 = test_fault_read,
.some_holes_6 = test_fault_read,
.some_holes_7 = test_fault_read,
.some_holes_8 = test_fault_read,
.some_holes_9 = test_fault_read,
.some_holes_10 = test_fault_read,
.some_holes_11 = test_fault_read,
.some_holes_12 = test_fault_read,
.all_holes_1 = test_fault_read,
.all_holes_2 = test_fault_read,
.all_holes_3 = test_fault_read,
.all_holes_4 = test_fault_read,
.null_entry = test_fault_read,
.nonresident_entry = test_fault_read,
.resident_entry = test_fault_read,
/* TODO move pages_resident from entry checker to object checker */
.shared_entry = test_is_unimplemented,
.shared_entry_discontiguous = test_is_unimplemented,
.shared_entry_partial = test_is_unimplemented,
.shared_entry_pairs = test_is_unimplemented,
.shared_entry_x1000 = test_is_unimplemented,
.cow_entry = test_fault_read,
.cow_unreferenced = test_fault_read,
.cow_nocow = test_fault_read,
.nocow_cow = test_fault_read,
.cow_unreadable = test_fault_read,
.cow_unwriteable = test_fault_read,
.permanent_entry = test_fault_read,
.permanent_before_permanent = test_fault_read,
.permanent_before_allocation = test_fault_read,
.permanent_before_allocation_2 = test_fault_read,
.permanent_before_hole = test_fault_read,
.permanent_after_allocation = test_fault_read,
.permanent_after_hole = test_fault_read,
.single_submap_single_entry = test_fault_read,
.single_submap_single_entry_first_pages = test_fault_read,
.single_submap_single_entry_last_pages = test_fault_read,
.single_submap_single_entry_middle_pages = test_fault_read,
.single_submap_oversize_entry_at_start = test_fault_read,
.single_submap_oversize_entry_at_end = test_fault_read,
.single_submap_oversize_entry_at_both = test_fault_read,
.submap_before_allocation = test_fault_read,
.submap_after_allocation = test_fault_read,
.submap_before_hole = test_fault_read,
.submap_after_hole = test_fault_read,
.submap_allocation_submap_one_entry = test_fault_read,
.submap_allocation_submap_two_entries = test_fault_read,
.submap_allocation_submap_three_entries = test_fault_read,
.submap_before_allocation_ro = test_fault_read,
.submap_after_allocation_ro = test_fault_read,
.submap_before_hole_ro = test_fault_read,
.submap_after_hole_ro = test_fault_read,
.submap_allocation_submap_one_entry_ro = test_fault_read,
.submap_allocation_submap_two_entries_ro = test_fault_read,
.submap_allocation_submap_three_entries_ro = test_fault_read,
.protection_single_000_000 = test_fault_read,
.protection_single_000_r00 = test_fault_read,
.protection_single_r00_r00 = test_fault_read,
.protection_single_000_0w0 = test_fault_read,
.protection_single_0w0_0w0 = test_fault_read,
.protection_single_000_rw0 = test_fault_read,
.protection_single_r00_rw0 = test_fault_read,
.protection_single_0w0_rw0 = test_fault_read,
.protection_single_rw0_rw0 = test_fault_read,
.protection_pairs_000_000 = test_fault_read,
.protection_pairs_000_r00 = test_fault_read,
.protection_pairs_000_0w0 = test_fault_read,
.protection_pairs_000_rw0 = test_fault_read,
.protection_pairs_r00_000 = test_fault_read,
.protection_pairs_r00_r00 = test_fault_read,
.protection_pairs_r00_0w0 = test_fault_read,
.protection_pairs_r00_rw0 = test_fault_read,
.protection_pairs_0w0_000 = test_fault_read,
.protection_pairs_0w0_r00 = test_fault_read,
.protection_pairs_0w0_0w0 = test_fault_read,
.protection_pairs_0w0_rw0 = test_fault_read,
.protection_pairs_rw0_000 = test_fault_read,
.protection_pairs_rw0_r00 = test_fault_read,
.protection_pairs_rw0_0w0 = test_fault_read,
.protection_pairs_rw0_rw0 = test_fault_read,
};
run_vm_tests("fault_read", __FILE__, &tests, argc, argv);
}
T_DECL(fault_write,
"perform write faults with various vm configurations")
{
vm_tests_t tests = {
.single_entry_1 = test_fault_write,
.single_entry_2 = test_fault_write,
.single_entry_3 = test_fault_write,
.single_entry_4 = test_fault_write,
.multiple_entries_1 = test_fault_write,
.multiple_entries_2 = test_fault_write,
.multiple_entries_3 = test_fault_write,
.multiple_entries_4 = test_fault_write,
.multiple_entries_5 = test_fault_write,
.multiple_entries_6 = test_fault_write,
.some_holes_1 = test_fault_write,
.some_holes_2 = test_fault_write,
.some_holes_3 = test_fault_write,
.some_holes_4 = test_fault_write,
.some_holes_5 = test_fault_write,
.some_holes_6 = test_fault_write,
.some_holes_7 = test_fault_write,
.some_holes_8 = test_fault_write,
.some_holes_9 = test_fault_write,
.some_holes_10 = test_fault_write,
.some_holes_11 = test_fault_write,
.some_holes_12 = test_fault_write,
.all_holes_1 = test_fault_write,
.all_holes_2 = test_fault_write,
.all_holes_3 = test_fault_write,
.all_holes_4 = test_fault_write,
.null_entry = test_fault_write,
.nonresident_entry = test_fault_write,
.resident_entry = test_fault_write,
/* TODO move pages_resident from entry checker to object checker */
.shared_entry = test_is_unimplemented,
.shared_entry_discontiguous = test_is_unimplemented,
.shared_entry_partial = test_is_unimplemented,
.shared_entry_pairs = test_is_unimplemented,
.shared_entry_x1000 = test_is_unimplemented,
.cow_entry = test_fault_write_cow_1st,
.cow_unreferenced = test_fault_write_cow_1st,
.cow_nocow = test_fault_write_cow_1st,
.nocow_cow = test_fault_write_cow_2nd,
.cow_unreadable = test_fault_write,
.cow_unwriteable = test_fault_write,
.permanent_entry = test_fault_write,
.permanent_before_permanent = test_fault_write,
.permanent_before_allocation = test_fault_write,
.permanent_before_allocation_2 = test_fault_write,
.permanent_before_hole = test_fault_write,
.permanent_after_allocation = test_fault_write,
.permanent_after_hole = test_fault_write,
.single_submap_single_entry = test_fault_write,
.single_submap_single_entry_first_pages = test_fault_write,
.single_submap_single_entry_last_pages = test_fault_write,
.single_submap_single_entry_middle_pages = test_fault_write,
.single_submap_oversize_entry_at_start = test_fault_write,
.single_submap_oversize_entry_at_end = test_fault_write,
.single_submap_oversize_entry_at_both = test_fault_write,
/* TODO: fix submap_allocation_submap tests */
.submap_before_allocation = test_fault_write,
.submap_after_allocation = test_fault_write,
.submap_before_hole = test_fault_write,
.submap_after_hole = test_fault_write,
.submap_allocation_submap_one_entry = test_is_unimplemented,
.submap_allocation_submap_two_entries = test_is_unimplemented,
.submap_allocation_submap_three_entries = test_is_unimplemented,
.submap_before_allocation_ro = test_fault_write,
.submap_after_allocation_ro = test_fault_write,
.submap_before_hole_ro = test_fault_write,
.submap_after_hole_ro = test_fault_write,
.submap_allocation_submap_one_entry_ro = test_is_unimplemented,
.submap_allocation_submap_two_entries_ro = test_is_unimplemented,
.submap_allocation_submap_three_entries_ro = test_is_unimplemented,
.protection_single_000_000 = test_fault_write,
.protection_single_000_r00 = test_fault_write,
.protection_single_r00_r00 = test_fault_write,
.protection_single_000_0w0 = test_fault_write,
.protection_single_0w0_0w0 = test_fault_write,
.protection_single_000_rw0 = test_fault_write,
.protection_single_r00_rw0 = test_fault_write,
.protection_single_0w0_rw0 = test_fault_write,
.protection_single_rw0_rw0 = test_fault_write,
.protection_pairs_000_000 = test_fault_write,
.protection_pairs_000_r00 = test_fault_write,
.protection_pairs_000_0w0 = test_fault_write,
.protection_pairs_000_rw0 = test_fault_write,
.protection_pairs_r00_000 = test_fault_write,
.protection_pairs_r00_r00 = test_fault_write,
.protection_pairs_r00_0w0 = test_fault_write,
.protection_pairs_r00_rw0 = test_fault_write,
.protection_pairs_0w0_000 = test_fault_write,
.protection_pairs_0w0_r00 = test_fault_write,
.protection_pairs_0w0_0w0 = test_fault_write,
.protection_pairs_0w0_rw0 = test_fault_write,
.protection_pairs_rw0_000 = test_fault_write,
.protection_pairs_rw0_r00 = test_fault_write,
.protection_pairs_rw0_0w0 = test_fault_write,
.protection_pairs_rw0_rw0 = test_fault_write,
};
run_vm_tests("fault_write", __FILE__, &tests, argc, argv);
}