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@
*
*/
#include <darwintest.h>
#include <darwintest_utils.h>
#include <os/thread_self_restrict.h>
#include <stdlib.h>
#include <sys/mman.h>
#include <System/machine/cpu_capabilities.h>
#include "exc_guard_helper.h"
#include "test_utils.h"
T_GLOBAL_META(
T_META_NAMESPACE("xnu.vm"),
T_META_RADAR_COMPONENT_NAME("xnu"),
T_META_RADAR_COMPONENT_VERSION("VM"),
T_META_OWNER("jharmening"),
T_META_CHECK_LEAKS(false),
T_META_RUN_CONCURRENTLY(true),
T_META_ALL_VALID_ARCHS(true));
typedef struct {
uint64_t ptr;
uint32_t size;
char test_pattern;
bool copy_expected;
bool should_fail;
bool upl_rw;
} upl_test_args;
T_DECL(vm_upl_ro_on_rw,
"Generate RO UPL against RW memory region")
{
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
upl_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .test_pattern = 'a',
.copy_expected = false, .should_fail = false, .upl_rw = false };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x800;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x1000;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
munmap(buf, buf_size);
}
T_DECL(vm_upl_ro_on_ro,
"Generate RO UPL against RO memory region")
{
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
T_QUIET; T_ASSERT_POSIX_SUCCESS(mprotect(buf, buf_size, PROT_READ), "mprotect");
upl_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .test_pattern = 'a',
.copy_expected = false, .should_fail = false, .upl_rw = false };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x800;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x1000;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
munmap(buf, buf_size);
}
T_DECL(vm_upl_rw_on_rw,
"Generate RW UPL against RW memory region")
{
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
upl_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .test_pattern = 'b',
.copy_expected = false, .should_fail = false, .upl_rw = true };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
T_QUIET; T_ASSERT_EQ(buf[i], (unsigned int)'b' + i,
"buf[%u]='%u' == '%u'",
i, buf[i], (unsigned int)'b' + i);
}
bzero(buf, buf_size);
args.ptr = (uint64_t)buf + 0x800;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
if ((i < (0x800 / sizeof(*buf))) || (i >= ((0x800 + args.size) / sizeof(*buf)))) {
T_QUIET; T_ASSERT_EQ(buf[i], 0,
"buf[%u]='%u' == 0", i, buf[i]);
} else {
T_QUIET; T_ASSERT_EQ(buf[i], (unsigned int)'b' + i - (unsigned int)(0x800 / sizeof(*buf)),
"buf[%u]='%u' == '%u'",
i, buf[i], (unsigned int)'b' + i - (unsigned int)(0x800 / sizeof(*buf)));
}
}
bzero(buf, buf_size);
args.ptr = (uint64_t)buf + 0x1000;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
if ((i < (0x1000 / sizeof(*buf))) || (i >= ((0x1000 + args.size) / sizeof(*buf)))) {
T_QUIET; T_ASSERT_EQ(buf[i], 0,
"buf[%u]='%u' == 0", i, buf[i]);
} else {
T_QUIET; T_ASSERT_EQ(buf[i], (unsigned int)'b' + i - (unsigned int)(0x1000 / sizeof(*buf)),
"buf[%u]='%u' == '%u'",
i, buf[i], (unsigned int)'b' + i - (unsigned int)(0x1000 / sizeof(*buf)));
}
}
munmap(buf, buf_size);
}
T_DECL(vm_upl_rw_on_ro,
"Generate RW UPL against RO memory region")
{
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
T_QUIET; T_ASSERT_POSIX_SUCCESS(mprotect(buf, buf_size, PROT_READ), "mprotect");
upl_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .test_pattern = 'b',
.copy_expected = false, .should_fail = true, .upl_rw = true };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x800;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x1000;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
munmap(buf, buf_size);
}
static bool
sptm_enabled(void)
{
int page_protection_type, err;
size_t size = sizeof(page_protection_type);
err = sysctlbyname("kern.page_protection_type", &page_protection_type, &size, NULL, 0);
T_ASSERT_POSIX_SUCCESS(err, "sysctl(\"kern.page_protection_type\");");
return page_protection_type == 2;
}
T_DECL(vm_upl_ro_on_rx,
"Generate RO UPL against RX memory region")
{
bool copy_expected = true;
#if TARGET_OS_OSX
/**
* For embedded targets, UPL creation against RX mappings should always produce a copy due to codesigning.
* For MacOS, a copy should only be produced if the SPTM is enabled, due to the SPTM's stricter requirements
* for DMA mappings of executable frame types.
*/
if (!sptm_enabled()) {
copy_expected = false;
}
#endif /* TARGET_OS_OSX */
upl_test_args args = { .ptr = (uint64_t)__builtin_return_address(0), .size = PAGE_SIZE, .test_pattern = 'a',
.copy_expected = copy_expected, .should_fail = false, .upl_rw = false };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr += 0x100;
args.size -= 0x200;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
}
T_DECL(vm_upl_rw_on_rx,
"Generate RW UPL against RX memory region")
{
upl_test_args args = { .ptr = (uint64_t)__builtin_return_address(0), .size = PAGE_SIZE, .test_pattern = 'a',
.copy_expected = true, .should_fail = true, .upl_rw = true };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr += 0x100;
args.size -= 0x200;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
}
T_DECL(vm_upl_ro_on_jit,
"Generate RO UPL against JIT memory region")
{
/**
* Direct RO UPLs against JIT pages should be allowed for non-SPTM targets.
* For SPTM targets, a copy is expected due to the SPTM's stricter requirements for DMA
* mappings of executable frame types.
*/
bool copy_expected = sptm_enabled();
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
if (os_thread_self_restrict_rwx_is_supported()) {
os_thread_self_restrict_rwx_to_rw();
}
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
upl_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .test_pattern = 'b',
.copy_expected = copy_expected, .should_fail = false, .upl_rw = false };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x800;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
args.ptr = (uint64_t)buf + 0x1000;
args.size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
munmap(buf, buf_size);
}
T_DECL(vm_upl_rw_on_jit,
"Generate RW UPL against JIT memory region")
{
if (process_is_translated()) {
/* TODO: Remove this once rdar://142438840 is fixed. */
T_SKIP("Guard exception handling does not work correctly with Rosetta (rdar://142438840), skipping...");
}
const size_t buf_size = 10 * PAGE_SIZE;
/**
* Direct RW UPLs against JIT pages should be allowed for non-SPTM targets.
* For SPTM targets, UPL creation should fail due to the SPTM's stricter requirements for DMA
* mappings of executable frame types.
*/
bool should_fail = sptm_enabled();
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
upl_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .test_pattern = 'b',
.copy_expected = false, .should_fail = should_fail, .upl_rw = true };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
/* Ensure that guard exceptions will not be fatal to the test process. */
enable_exc_guard_of_type(GUARD_TYPE_VIRT_MEMORY);
/**
* Iterate 3 times to guarantee buffer offsets that are neither 4K nor 16K aligned,
* and 4K but not necessarily 16K aligned.
*/
for (int i = 0; i < 2; i++) {
exc_guard_helper_info_t exc_info;
bool caught_exception =
block_raised_exc_guard_of_type(GUARD_TYPE_VIRT_MEMORY, &exc_info, ^{
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
});
if (args.should_fail) {
T_ASSERT_TRUE(caught_exception, "Failing test should also throw guard exception");
T_ASSERT_EQ(exc_info.guard_flavor, kGUARD_EXC_SEC_UPL_WRITE_ON_EXEC_REGION,
"Failing test throws the expected guard exception flavor");
T_ASSERT_EQ(exc_info.catch_count, 1, "Failing test should throw exactly one guard exception");
} else {
T_ASSERT_FALSE(caught_exception, "Passing test should not throw guard exception");
}
args.ptr += 0x800;
args.size -= 0x1000;
}
munmap(buf, buf_size);
}
T_DECL(vm_upl_ro_on_commpage,
"Generate RO UPL against comm page")
{
#if !TARGET_OS_OSX
T_SKIP("Comm page only guaranteed to be within user address range on MacOS, skipping...");
#else
#ifndef __arm64__
T_SKIP("Comm page only has UPL-incompatible mapping on arm64, skipping...");
#else
upl_test_args args = { .ptr = (uint64_t)_COMM_PAGE_START_ADDRESS, .size = 0x1000, .test_pattern = 'b',
.copy_expected = false, .should_fail = true, .upl_rw = false };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
#endif /* !defined(__arm64__) */
#endif /* !TARGET_OS_OSX */
}
T_DECL(vm_upl_partial_cow,
"Generate a UPL that requires CoW setup for part of an object")
{
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
/*
* Mark a portion of the buffer RO, which will split off a separate vm_map_entry backed by the same
* vm_object. This will produce an internal COPY_SYMMETRIC object with refcount > 1, which is the
* baseline requirement for partial CoW setup by vm_map_create_upl().
*/
T_QUIET; T_ASSERT_POSIX_SUCCESS(mprotect((char*)buf + (8 * PAGE_SIZE), 2 * PAGE_SIZE, PROT_READ), "mprotect");
/*
* Request a non-page-aligned UPL against the RW region of the buffer, to ensure that partial CoW
* setup still ultimately uses a page-aligned buffer as required for vm_map_entry clipping.
*/
upl_test_args args = { .ptr = (uint64_t)buf + 0x800, .size = 2 * PAGE_SIZE, .test_pattern = 'b',
.copy_expected = false, .should_fail = false, .upl_rw = true };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl)");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
if ((i < (0x800 / sizeof(*buf))) || (i >= ((0x800 + args.size) / sizeof(*buf)))) {
T_QUIET; T_ASSERT_EQ(buf[i], (unsigned int)'a' + i,
"buf[%u]='%u' == '%u'", i, buf[i], (unsigned int)'a' + i);
} else {
T_QUIET; T_ASSERT_EQ(buf[i], (unsigned int)'b' + i - (unsigned int)(0x800 / sizeof(*buf)),
"buf[%u]='%u' == '%u'",
i, buf[i], (unsigned int)'b' + i - (unsigned int)(0x800 / sizeof(*buf)));
}
}
munmap(buf, buf_size);
}
typedef struct {
uint64_t ptr;
uint32_t size;
bool upl_rw;
bool should_fail;
bool exec_fault;
} upl_object_test_args;
T_DECL(vm_upl_rw_on_exec_object,
"Attempt to create a writable UPL against an object containing executable pages")
{
/**
* This test is meant to exercise functionality that is currently SPTM-specific.
* It also relies on the assumption that JIT regions are faulted in an all-or-nothing
* manner, so that the write faults generated by our buffer fill below will also
* produce executable mappings of the underlying JIT pages. This happens to hold
* true on SPTM-enabled devices because all of them use xPRR, but may not hold true
* in general.
*/
if (!sptm_enabled()) {
T_SKIP("Exec object test only supported on SPTM-enabled devices, skipping...");
}
if (process_is_translated()) {
/* TODO: Remove this once rdar://142438840 is fixed. */
T_SKIP("Guard exception handling does not work correctly with Rosetta (rdar://142438840), skipping...");
}
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
if (os_thread_self_restrict_rwx_is_supported()) {
os_thread_self_restrict_rwx_to_rw();
}
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
/* Ensure that guard exceptions will not be fatal to the test process. */
enable_exc_guard_of_type(GUARD_TYPE_VIRT_MEMORY);
upl_object_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .upl_rw = true, .should_fail = true, .exec_fault = false };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
exc_guard_helper_info_t exc_info;
bool caught_exception =
block_raised_exc_guard_of_type(GUARD_TYPE_VIRT_MEMORY, &exc_info, ^{
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_object", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_object)");
});
if (args.should_fail) {
T_ASSERT_TRUE(caught_exception, "Failing test should also throw guard exception");
T_ASSERT_EQ(exc_info.guard_flavor, kGUARD_EXC_SEC_IOPL_ON_EXEC_PAGE,
"Failing test throws the expected guard exception flavor");
T_ASSERT_EQ(exc_info.catch_count, 1, "Failing test should throw exactly one guard exception");
} else {
T_ASSERT_FALSE(caught_exception, "Passing test should not throw guard exception");
}
}
T_DECL(vm_upl_ro_with_exec_fault,
"Attempt to exec-fault a region while a UPL is in-flight for that region")
{
/**
* This test is meant to exercise functionality that is currently SPTM-specific.
*/
if (!sptm_enabled()) {
T_SKIP("Exec-fault test only supported on SPTM-enabled devices, skipping...");
}
if (process_is_translated()) {
/* TODO: Remove this once rdar://142438840 is fixed. */
T_SKIP("Guard exception handling does not work correctly with Rosetta (rdar://142438840), skipping...");
}
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE | PROT_EXEC, MAP_ANON | MAP_PRIVATE | MAP_JIT, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
if (os_thread_self_restrict_rwx_is_supported()) {
os_thread_self_restrict_rwx_to_rw();
}
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
if (os_thread_self_restrict_rwx_is_supported()) {
os_thread_self_restrict_rwx_to_rx();
}
/* Ensure that guard exceptions will not be fatal to the test process. */
enable_exc_guard_of_type(GUARD_TYPE_VIRT_MEMORY);
upl_object_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .upl_rw = false, .should_fail = false, .exec_fault = true };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
exc_guard_helper_info_t exc_info;
bool caught_exception =
block_raised_exc_guard_of_type(GUARD_TYPE_VIRT_MEMORY, &exc_info, ^{
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_object", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_object)");
});
T_ASSERT_TRUE(caught_exception, "Exec fault should throw guard exception");
T_ASSERT_EQ(exc_info.guard_flavor, kGUARD_EXC_SEC_EXEC_ON_IOPL_PAGE,
"Attempted exec fault throws the expected guard exception flavor");
T_ASSERT_EQ(exc_info.catch_count, 1, "Attempted exec fault should throw exactly one guard exception");
}
typedef struct {
uint64_t ptr;
uint64_t upl_base;
uint32_t size;
uint32_t upl_size;
bool upl_rw;
} upl_submap_test_args;
T_DECL(vm_upl_ro_on_submap,
"Generate RO UPL against a submap region")
{
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
upl_submap_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .upl_base = 0x180000000ULL,
.upl_size = buf_size, .upl_rw = false };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_submap", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_submap)");
args.upl_base += 0x800;
args.upl_size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_submap", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_submap)");
args.upl_base += 0x800;
args.upl_size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_submap", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_submap)");
munmap(buf, buf_size);
}
T_DECL(vm_upl_rw_on_submap,
"Generate RW UPL against a submap region")
{
const size_t buf_size = 10 * PAGE_SIZE;
unsigned int *buf = mmap(NULL, buf_size, PROT_READ | PROT_WRITE, MAP_ANON | MAP_PRIVATE, -1, 0);
T_QUIET; T_ASSERT_NE_PTR(buf, MAP_FAILED, "map buffer");
for (unsigned int i = 0; i < (buf_size / sizeof(*buf)); i++) {
buf[i] = (unsigned int)'a' + i;
}
upl_submap_test_args args = { .ptr = (uint64_t)buf, .size = buf_size, .upl_base = 0x180000000ULL,
.upl_size = buf_size, .upl_rw = true };
int64_t addr = (int64_t)&args;
int64_t result = 0;
size_t s = sizeof(result);
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_submap", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_submap)");
args.upl_base += 0x800;
args.upl_size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_submap", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_submap");
args.upl_base += 0x800;
args.upl_size -= 0x1000;
T_ASSERT_POSIX_SUCCESS(sysctlbyname("debug.test.vm_upl_submap", &result, &s, &addr, sizeof(addr)),
"sysctlbyname(debug.test.vm_upl_submap)");
munmap(buf, buf_size);
}