Loading...
--- xnu/xnu-12377.101.15/tests/try_read_write.c
+++ /dev/null
@@ -1,287 +0,0 @@
-/*
- * 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@
- */
-
-/*
- * try_read_write.c
- *
- * Helper functions for userspace tests to read or write memory and
- * verify that EXC_BAD_ACCESS is or is not generated by that operation.
- */
-
-#include <assert.h>
-#include <stdbool.h>
-#include <stdatomic.h>
-#include <ptrauth.h>
-#include <darwintest.h>
-#include <dispatch/dispatch.h>
-
-#include "exc_helpers.h"
-#include "try_read_write.h"
-
-/*
- * -- Implementation overview --
- *
- * try_read_byte() and try_write_byte() operate by performing
- * a read or write instruction with a Mach exception handler
- * in place.
- *
- * The exception handler catches EXC_BAD_ACCESS. If the bad access
- * came from our designated read or write instructions then it
- * records the exception that occurred to storage prepared by
- * try_read_byte() or try_write_byte() and moves that thread's
- * program counter to resume execution and recover from the exception.
- *
- * Unrecognized exceptions, and EXC_BAD_ACCESS exceptions from
- * unrecognized instructions, either go uncaught or are caught and
- * re-raised. In either case they lead to an ordinary crash. This
- * means we don't get false positives where the test expects one
- * crash but incorrectly passes after crashing in some unrelated way.
- * We can be precise about what the fault was and where it came from.
- *
- * We use Mach exceptions instead of signals because
- * on watchOS signal handlers do not receive the thread
- * state so they cannot recover from the signal.
- *
- * try_read_write_exception_handler()
- * our exception handler, installed using tests/exc_helpers.c
- *
- * read_byte() and write_byte()
- * our designated read and write instructions, recognized by
- * the exception handler and specially structured to allow
- * recovery by changing the PC
- *
- * try_read_write_exception_received_t
- * storage to record the caught exception
- */
-
-typedef struct {
- kern_return_t exception_kr; /* EXC_BAD_ADDRESS sub-code */
- uint64_t exception_pc; /* PC of faulting instruction */
- uint64_t exception_memory; /* Memory address of faulting access */
-} try_read_write_exception_received_t;
-
-/*
- * try_read_write_read_byte() and try_read_write_write_byte() are functions that
- * read or write memory as their first instruction.
- * Used to test memory access that may provoke an exception.
- *
- * If the memory operation completes successfully,
- * *out_exception_received is unchanged.
- *
- * If an exception is received during the memory operation,
- * *out_exception_received is populated with the exception's
- * contents by the exception handler itself.
- *
- * try_read_write_exception_handler() below checks if the exception PC
- * is equal to one of these functions. The first instruction here must
- * be the memory access instruction.
- *
- * try_read_write_exception_handler() below increments the PC by four bytes.
- * The memory access instruction must be padded to exactly four bytes.
- */
-
-static uint64_t __attribute__((naked))
-try_read_write_read_byte(
- try_read_write_exception_received_t * const out_exception_received,
- mach_vm_address_t addr)
-{
-#if __arm64__
- asm("\n ldrb w0, [x1]"
- "\n ret");
-#elif __x86_64__
- asm("\n movb (%rsi), %al"
- "\n nop" /* pad load to four bytes */
- "\n nop"
- "\n ret");
-#else
-# error unknown architecture
-#endif
-}
-
-static void __attribute__((naked))
-try_read_write_write_byte(
- try_read_write_exception_received_t * const out_exception_received,
- mach_vm_address_t addr,
- uint8_t value)
-{
-#if __arm64__
- asm("\n strb w2, [x1]"
- "\n ret");
-#elif __x86_64__
- asm("\n movb %dl, (%rsi)"
- "\n nop" /* pad store to four bytes */
- "\n nop"
- "\n ret");
-#else
-# error unknown architecture
-#endif
-}
-
-/*
- * Given thread state for an exception at read_byte() or write_byte(),
- * return the pointer to the exception received info
- * that the exception handler should populate.
- */
-static try_read_write_exception_received_t *
-get_exception_received_info(thread_state_t in_state)
-{
- /*
- * Pointer to info is in the first parameter register
- * for both read_byte() and write_byte().
- */
-#if __arm64__
- arm_thread_state64_t *arm_state = (arm_thread_state64_t *)in_state;
- return (try_read_write_exception_received_t *)arm_state->__x[0];
-#elif __x86_64__
- x86_thread_state64_t *x86_state = (x86_thread_state64_t *)in_state;
- return (try_read_write_exception_received_t *)x86_state->__rdi;
-#else
-# error unknown architecture
-#endif
-}
-
-/*
- * Mach exception handler for EXC_BAD_ACCESS called by exc_helpers.
- * Returns the number of bytes to advance the PC to resolve the exception.
- */
-static size_t
-try_read_write_exception_handler(
- exception_type_t exception,
- mach_exception_data_t codes,
- uint64_t exception_pc,
- thread_state_t in_state,
- mach_msg_type_number_t in_state_count __unused)
-{
- assert(exception == EXC_BAD_ACCESS);
-
- uint64_t read_byte_pc = (uint64_t)ptrauth_strip(&try_read_write_read_byte, ptrauth_key_function_pointer);
- uint64_t write_byte_pc = (uint64_t)ptrauth_strip(&try_read_write_write_byte, ptrauth_key_function_pointer);
-
- if (exception_pc != read_byte_pc && exception_pc != write_byte_pc) {
- /* this exception isn't one of ours - re-raise it */
- if (verbose_exc_helper) {
- T_LOG("not a try_read_write exception");
- }
- return EXC_HELPER_HALT;
- }
-
- try_read_write_exception_received_t *info = get_exception_received_info(in_state);
- assert(info->exception_kr == 0); /* no nested exceptions allowed */
-
- info->exception_pc = exception_pc;
- info->exception_kr = codes[0];
- info->exception_memory = codes[1];
- if (verbose_exc_helper) {
- T_LOG("try_read_write exception: pc 0x%llx kr %d mem 0x%llx",
- info->exception_pc, info->exception_kr, info->exception_memory);
- }
-
- /* advance pc by 4 bytes to recover */
- return 4;
-}
-
-/*
- * Begin try_read_write exception handling on this thread.
- */
-static void
-begin_expected_exceptions(void)
-{
- /* global state */
- static dispatch_once_t try_read_write_initializer;
- static mach_port_t try_read_write_exc_port;
-
- /* thread-local state */
- static __thread bool try_read_write_thread_is_initialized;
-
- dispatch_once(&try_read_write_initializer, ^{
- /*
- * Create an exc_helper exception handler for all try_read_write threads.
- * We don't use exc_helper's default EXCEPTION_STATE_IDENTITY
- * because that flavor can't change thread state to recover
- * when Developer Mode is off (such as Release builds).
- */
- try_read_write_exc_port = create_exception_port_behavior64(EXC_MASK_BAD_ACCESS, EXCEPTION_STATE);
- repeat_exception_handler_behavior64(try_read_write_exc_port, try_read_write_exception_handler, EXCEPTION_STATE);
- });
-
- if (try_read_write_thread_is_initialized == false) {
- /* Install the exception handler on this thread. */
- set_thread_exception_port_behavior64(try_read_write_exc_port, EXC_MASK_BAD_ACCESS, EXCEPTION_STATE);
- try_read_write_thread_is_initialized = true;
- }
-}
-
-/*
- * End try_read_write exception handling on this thread and evaluate the result.
- * Returns true if the operation was successful. Sets *out_error = KERN_SUCCESS.
- * Returns false if there was an exception. Sets *out_error to the exception's error.
- */
-static bool
-end_expected_exceptions(
- try_read_write_exception_received_t info,
- mach_vm_address_t addr,
- kern_return_t * const out_error)
-{
- /*
- * exception_pc was verified inside the exception handler.
- * exception_kr will be verified by the caller of try_read/write_byte.
- * Verify exception_memory here.
- */
- if (info.exception_kr != KERN_SUCCESS) {
- assert(info.exception_memory == addr);
- }
-
- *out_error = info.exception_kr;
- return info.exception_kr == KERN_SUCCESS;
-}
-
-extern bool
-try_read_byte(
- mach_vm_address_t addr,
- uint8_t * const out_byte,
- kern_return_t * const out_error)
-{
- try_read_write_exception_received_t info = { KERN_SUCCESS, 0, 0 };
-
- begin_expected_exceptions();
- *out_byte = try_read_write_read_byte(&info, addr);
- return end_expected_exceptions(info, addr, out_error);
-}
-
-extern bool
-try_write_byte(
- mach_vm_address_t addr,
- uint8_t byte,
- kern_return_t * const out_error)
-{
- try_read_write_exception_received_t info = { KERN_SUCCESS, 0, 0 };
-
- begin_expected_exceptions();
- try_read_write_write_byte(&info, addr, byte);
- return end_expected_exceptions(info, addr, out_error);
-}