/*
* 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.h
*
* Generator and checker of userspace virtual memory configurations.
*/
#ifndef VM_CONFIGURATOR_H
#define VM_CONFIGURATOR_H
/*
* -- Dramatis personae --
*
* vm_entry_template_t
* Specification of a VM entry to create,
* or a hole in VM address space to skip over.
* Used to describe and create the VM state for the start of a test.
*
* vm_object_template_t
* Specification of a VM object to create for entries to copy or share.
* Used to describe and create the VM state for the start of a test.
*
* vm_config_t
* Specification of one or more contiguous VM entries,
* plus a test name and an address range within that VM
* space that is the range to be tested.
* Used to describe and create the VM state for the start of a test.
*
* vm_entry_checker_t
* Describes the expected state of a VM entry or a hole,
* and verifies that the live VM state matches the expected state.
* Updated by test code as test operations are performed.
* Used to verify the VM state during and after a test.
*
* vm_object_checker_t
* Describes the expected state of a VM object
* and verifies that the live VM state matches the expected state
* Updated by test code as test operations are performed.
* Used to verify the VM state during and after a test.
*
* -- Outline of a test --
*
* 1. Describe the desired initial memory state
* with arrays of vm_entry_template_t and vm_object_template_t.
* 2. Call create_vm_state() to allocate the specified VM entries
* and lists of vm_entry_checker_t and vm_object_checker_t
* that match the newly-allocated state.
* 3. Perform the VM operations to be tested. Update the checkers
* with the state changes that you expect. If some field's value
* becomes indeterminate, or difficult to specify and unimportant
* for your test, disable that field in the checker.
* 4. Call verify_vm_state() to compare the live
* VM state to the checker's expected state.
* 5. Optionally repeat steps 3 and 4 to test a sequence of VM operations.
*
* See vm_configurator_tests.h for a set of templates used by
* many VM syscall tests, and some details on how to run them.
*/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <stdbool.h>
#include <assert.h>
#include <mach/mach.h>
#include <mach/mach_vm.h>
#include <mach/vm_prot.h>
#include <mach/vm_param.h>
#include <mach/vm_region.h>
#include <mach/vm_inherit.h>
#include <mach/vm_behavior.h>
#include <mach/vm_statistics.h>
#include <darwintest.h>
#include <darwintest_utils.h>
#include <test_utils.h>
/*
* Set Verbose = true to log the complete VM state, both expected and actual,
* every time it is checked.
* Initialized from environment variable VERBOSE
*/
extern bool Verbose;
/*
* Return values from individual test functions.
* These are ordered from "best" to "worst".
*
* TODO: docs
*/
typedef enum {
TestSucceeded = 1,
TestFailed,
} test_result_t;
static inline test_result_t
worst_result(test_result_t *list, unsigned count)
{
test_result_t worst = TestSucceeded;
for (unsigned i = 0; i < count; i++) {
if (list[i] > worst) {
worst = list[i];
}
}
return worst;
}
typedef enum {
DontFill = 0, /* must be zero */
Fill = 1
} fill_pattern_mode_t;
typedef struct {
fill_pattern_mode_t mode;
uint64_t pattern;
} fill_pattern_t;
/*
* EndObjects: for END_OBJECTS array terminator
* Deinited: an object that is no longer referenced and whose checker is now
* depopulated but is still allocated because some checker list may point to it
* Anonymous: anonymous memory such as vm_allocate()
* SubmapObject: an "object" that is really a submap
* TODO: support named/pageable objects
*/
typedef enum {
FreedObject = 0, /* use after free, shouldn't happen */
EndObjects,
Deinited,
Anonymous,
SubmapObject,
} vm_object_template_kind_t;
/*
* struct vm_object_template_t
* Declaratively specify VM objects to be created.
*/
typedef struct vm_object_template_s {
vm_object_template_kind_t kind;
mach_vm_size_t size; /* size 0 means auto-compute from entry sizes */
fill_pattern_t fill_pattern;
struct {
struct vm_entry_template_s *entries;
struct vm_object_template_s *objects;
unsigned entry_count;
unsigned object_count;
} submap;
} vm_object_template_t;
/*
* Convenience macro for initializing a vm_object_template_t.
* The macro sets all template fields to a default value.
* You may override any field using designated initializer syntax.
*
* Example usage:
* // all default values
* vm_object_template()
*
* // default, with custom size and fill pattern
* vm_object_template(
* .size = 20 * PAGE_SIZE,
* .fill_pattern = 0x1234567890abcdef)
*/
#pragma clang diagnostic ignored "-Wgnu-zero-variadic-macro-arguments"
#define vm_object_template(...) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
(vm_object_template_t){ \
.size = 0, /* auto-computed */ \
.kind = Anonymous, \
.fill_pattern = {.mode = DontFill}, \
__VA_ARGS__ \
} \
_Pragma("clang diagnostic pop")
/* Convenience for submap objects */
#define submap_object_template(...) \
vm_object_template(.kind = SubmapObject, __VA_ARGS__)
/*
* EndEntries: for END_ENTRIES array terminator
* Allocation: an ordinary VM entry
* Hole: an unallocated range of the address space.
* Submap: a mapping of a submap
*/
typedef enum {
EndEntries = 0,
Allocation,
Hole,
Submap,
} vm_entry_template_kind_t;
/*
* struct vm_entry_template_t
* Declaratively specify VM entries to be created.
*/
typedef struct vm_entry_template_s {
mach_vm_size_t size;
vm_entry_template_kind_t kind;
/*
* NULL object means either null vm_object_t or anonymous zerofilled
* memory, depending on the requirements of the other settings.
* (For example, non-zero wire count faults in the pages
* so it is no longer a null vm_object_t.)
* Used when .kind == Allocation.
*/
vm_object_template_t *object;
mach_vm_offset_t offset;
vm_prot_t protection;
vm_prot_t max_protection;
vm_inherit_t inheritance;
vm_behavior_t behavior;
bool permanent;
/* New entry gets vm_wire'd this many times. */
uint16_t user_wired_count;
/*
* User tag may be a specific value, or autoincrementing.
*
* An autoincrementing tag is assigned by create_vm_state()
* in the VM_MEMORY_APPLICATION_SPECIFIC_1-16 range. Adjacent
* autoincrementing entries get distinct tags. This can be
* used to stop the VM from simplifying/coalescing vm entries
* that you want to remain separate.
*/
uint16_t user_tag;
#define VM_MEMORY_TAG_AUTOINCREMENTING 256
uint8_t share_mode;
/*
* Code to update when adding new fields:
* vm_entry_template() macro
* create_vm_state() function
*/
} vm_entry_template_t;
/*
* Default size for vm_entries created by this generator
* Some tests require that this be above some minimum.
* 64 * PAGE_SIZE is big enough that 1/4 of an entry is
* still over the 32KB physical copy limit inside vm_map_copyin.
*/
#define DEFAULT_ENTRY_SIZE (64 * (mach_vm_address_t)PAGE_SIZE)
/*
* Default size for address ranges that cover only part of a vm_entry.
* Some tests require that this be above some minimum.
*/
#define DEFAULT_PARTIAL_ENTRY_SIZE (DEFAULT_ENTRY_SIZE / 2u)
/*
* Unnesting of submap nested pmaps occurs at L[N-1] page table
* boundaries (pmap "twig"). By default we avoid crossing those
* boundaries in tests because it affects the unnested map entries
* in the parent map.
* TODO: don't hardcode this, get it from pmap somehow
*/
#define SUBMAP_ALIGNMENT_MASK (0x2000000ull - 1)
/*
* Convenience macro for initializing a vm_entry_template_t.
* The macro sets all template fields to a default value.
* You may override any field using designated initializer syntax.
*
* Example usage:
* // all default values
* vm_entry_template()
*
* // default, with custom size and protections
* vm_entry_template(
* .size = 20 * PAGE_SIZE,
* .protection = VM_PROT_READ,
* .max_protection = VM_PROT_READ | VM_PROT_WRITE)
*/
#define vm_entry_template(...) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
(vm_entry_template_t){ \
.size = DEFAULT_ENTRY_SIZE, \
.kind = Allocation, \
.object = NULL, \
.offset = 0, \
.protection = VM_PROT_READ | VM_PROT_WRITE, \
.max_protection = VM_PROT_READ | VM_PROT_WRITE, \
.inheritance = VM_INHERIT_DEFAULT, /* inherit_copy */ \
.behavior = VM_BEHAVIOR_DEFAULT, \
.permanent = false, \
.user_wired_count = 0, \
.user_tag = VM_MEMORY_TAG_AUTOINCREMENTING, \
.share_mode = SM_EMPTY, \
__VA_ARGS__ \
} \
_Pragma("clang diagnostic pop")
/* Convenience for submap entries */
#define submap_entry_template(...) \
vm_entry_template(.kind = Submap, __VA_ARGS__)
/*
* Convenience templates.
* END_ENTRIES and END_OBJECTS: terminates a template list
* passed to create_vm_state() instead of passing an array count.
* (useful for hand-written template array initializers)
* guard_entry_template: an allocation that defaults to
* prot/max NONE/NONE and tag VM_MEMORY_GUARD
* hole_template: an unallocated hole in the address space.
*/
extern vm_object_template_t END_OBJECTS;
extern vm_entry_template_t END_ENTRIES;
extern vm_entry_template_t guard_entry_template;
extern vm_entry_template_t hole_template;
/*
* Count the number of templates in an END_TEMPLATE-terminated array.
*/
extern unsigned
count_templates(const vm_entry_template_t *templates);
/*
* struct vm_entry_attribute_list_t
* A list of checkable entry attributes with one bool for each.
* Used to record which attributes should be verified by a checker,
* or which attributes failed to match during verification.
*/
typedef struct {
union {
uint64_t bits;
struct {
uint64_t address_attr:1;
uint64_t size_attr:1;
uint64_t object_attr:1;
uint64_t protection_attr:1;
uint64_t max_protection_attr:1;
uint64_t inheritance_attr:1;
uint64_t behavior_attr:1;
uint64_t permanent_attr:1;
uint64_t user_wired_count_attr:1;
uint64_t user_tag_attr:1;
uint64_t is_submap_attr:1;
uint64_t submap_depth_attr:1;
uint64_t object_offset_attr:1;
uint64_t pages_resident_attr:1;
uint64_t share_mode_attr:1;
};
};
/*
* Code to update when adding new fields:
* dump_checker_info()
* vm_entry_attributes_with_default macro
* verify_allocation()
*/
} vm_entry_attribute_list_t;
/*
* struct vm_object_attribute_list_t
* A list of checkable entry attributes with one bool for each.
* Used to record which attributes should be verified by a checker,
* or which attributes failed to match during verification.
*/
typedef struct {
union {
uint64_t bits;
struct {
uint64_t object_id_attr:1;
uint64_t size_attr:1;
uint64_t ref_count_attr:1;
uint64_t shadow_depth_attr:1;
uint64_t fill_pattern_attr:1;
};
};
/*
* Code to update when adding new fields:
* dump_checker_info()
* vm_object_attributes_with_default macro
* verify_allocation()
*/
} vm_object_attribute_list_t;
/*
* vm_entry_attributes_with_default() returns a vm_entry_attribute_list_t,
* with all attributes set to `default_value`, and the caller can set individual
* attributes to other values using designated initializer syntax.
*/
#define vm_entry_attributes_with_default(default_value, ...) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
(vm_entry_attribute_list_t){ \
.address_attr = (default_value), \
.size_attr = (default_value), \
.object_attr = (default_value), \
.protection_attr = (default_value), \
.max_protection_attr = (default_value), \
.inheritance_attr = (default_value), \
.behavior_attr = (default_value), \
.permanent_attr = (default_value), \
.user_wired_count_attr = (default_value), \
.user_tag_attr = (default_value), \
.is_submap_attr = (default_value), \
.submap_depth_attr = (default_value), \
.object_offset_attr = (default_value), \
.pages_resident_attr = (default_value), \
.share_mode_attr = (default_value), \
__VA_ARGS__ \
} \
_Pragma("clang diagnostic pop")
/*
* vm_object_attributes_with_default() returns a vm_object_attribute_list_t,
* with all attributes set to `default_value`, and the caller can set individual
* attributes to other values using designated initializer syntax.
*/
#define vm_object_attributes_with_default(default_value, ...) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic ignored \"-Winitializer-overrides\"") \
_Pragma("clang diagnostic ignored \"-Wmissing-field-initializers\"") \
(vm_object_attribute_list_t){ \
.object_id_attr = (default_value), \
.size_attr = (default_value), \
.ref_count_attr = (default_value), \
.shadow_depth_attr = (default_value), \
.fill_pattern_attr = (default_value), \
__VA_ARGS__ \
} \
_Pragma("clang diagnostic pop")
/*
* Description of a checker's current knowledge of an object's ID.
* object_is_unknown: object'd ID is unknown; it may be null
* object_has_unknown_nonnull_id: object's ID is expected to be non-null,
* but its actual value is unknown
* object_has_known_id: object's ID is expected to be checker->object_id
*
* During verification unknown object IDs are learned by reading them from the
* actual VM state. The learned IDs are applied to subsequent verifications or
* to subsequent uses of the same object in the same verification.
*/
typedef enum {
object_is_unknown = 0,
object_has_unknown_nonnull_id,
object_has_known_id
} object_id_mode_t;
/*
* struct vm_object_checker_t
* Maintain and verify expected state of a VM object.
*/
typedef struct vm_object_checker_s {
struct vm_object_checker_s *prev;
struct vm_object_checker_s *next;
vm_object_template_kind_t kind;
vm_object_attribute_list_t verify;
bool deinited;
uint64_t object_id;
object_id_mode_t object_id_mode;
/*
* This is the count of references to this object specifically.
* vm_region's reported ref_count also includes references to
* the shadow chain's objects, minus the shadow chain's references
* to each other.
*/
unsigned self_ref_count;
mach_vm_size_t size;
fill_pattern_t fill_pattern;
/*
* Shadow chain.
* object->shadow moves away from entry.
* object->shadow is refcounted.
*/
struct vm_object_checker_s *shadow;
/*
* Checkers for submap contents.
* These checkers are configured for a mapping of the whole
* submap at address 0. Verification of actual remappings will
* need to compensate for address offsets and bounds clipping.
*/
struct checker_list_s *submap_checkers;
/*
* Code to update when adding new fields:
* struct vm_object_attribute_list_t
* make_null_object_checker()
* make_anonymous_object_checker()
* make_submap_object_checker()
* dump_checker_info()
* verify_allocation()
* object_checker_clone()
*/
} vm_object_checker_t;
/*
* Create a new object checker duplicating an existing checker.
* The new object is:
* - zero self_ref_count
* - unknown object_id
* - not linked into any checker_list
*/
extern vm_object_checker_t *
object_checker_clone(vm_object_checker_t *obj_checker);
/*
* struct vm_entry_checker_t
* Maintain and verify expected state of a VM map entry.
*
* The `verify` bitmap specifies which properties should be checked.
* If a property's value is indeterminate, or is difficult to specify
* and not important to the test, that check can be disabled.
*
* Checkers are kept in a doubly-linked list in address order,
* similar to vm_map_entry_t but it is not a circular list.
* Submaps are recursive: the top-level list contains a Submap checker,
* and the Submap checker has its own list of contained checkers.
*/
typedef struct vm_entry_checker_s {
struct vm_entry_checker_s *prev;
struct vm_entry_checker_s *next;
vm_entry_template_kind_t kind;
vm_entry_attribute_list_t verify;
mach_vm_address_t address;
mach_vm_size_t size;
vm_object_checker_t *object;
vm_prot_t protection;
vm_prot_t max_protection;
vm_inherit_t inheritance;
vm_behavior_t behavior;
bool permanent;
uint16_t user_wired_count;
uint8_t user_tag;
bool is_submap; /* true when entry is a parent map's submap entry */
uint32_t submap_depth; /* non-zero when entry is a submap's content */
uint64_t object_offset;
uint32_t pages_resident; /* TODO: track this in the object checker instead */
bool needs_copy;
/* share_mode is computed from other entry and object attributes */
/*
* Code to update when adding new fields:
* struct vm_entry_attribute_list_t
* make_checker_for_anonymous_private()
* make_checker_for_vm_allocate()
* make_checker_for_shared()
* make_checker_for_submap()
* dump_checker_info()
* verify_allocation()
* checker_simplify_left()
*/
} vm_entry_checker_t;
/*
* A list of consecutive entry checkers. May be a subset of the entire doubly-linked list.
*/
typedef struct {
vm_entry_checker_t *head;
vm_entry_checker_t *tail;
} entry_checker_range_t;
/*
* Count the number of entries between
* checker_range->head and checker_range->tail, inclusive.
*/
extern unsigned
checker_range_count(entry_checker_range_t checker_range);
/*
* Return the start address of the first entry in a range.
*/
extern mach_vm_address_t
checker_range_start_address(entry_checker_range_t checker_range);
/*
* Return the end address of the last entry in a range.
*/
extern mach_vm_address_t
checker_range_end_address(entry_checker_range_t checker_range);
/*
* Return size of all entries in a range.
*/
extern mach_vm_size_t
checker_range_size(entry_checker_range_t checker_range);
/*
* Loop over all checkers between
* entry_range->head and entry_range->tail, inclusive.
* Does visit any submap parent entry.
* Does not descend into submap contents.
*
* You may clip_left the current checker. The new left entry is not visited.
* You may clip_right the current checker. The new right entry is visited next.
* You may not delete the current checker, unless you also immediately break the loop.
*/
#define FOREACH_CHECKER(checker, entry_range) \
for (vm_entry_checker_t *checker = (entry_range).head; \
checker != (entry_range).tail->next; \
checker = checker->next)
/*
* The list of all entry and object checkers.
* The first and last entries may be changed by the test.
* The first object is the common null object, so it should not change.
*
* Submaps get their own checker_list_t. A submap checker
* list stores checkers for the submap's map entries.
* It does not store any objects; a single global list of objects is
* maintained in the top-level checker list so it can be searched by ID.
*
* submap_slide keeps track of a temporary address offset applied
* to the contained checkers. This is used for submap contents.
*/
typedef struct checker_list_s {
struct checker_list_s *parent;
entry_checker_range_t entries;
vm_object_checker_t *objects; /* must be NULL in submaps */
uint64_t submap_slide;
bool is_slid;
} checker_list_t;
#define FOREACH_OBJECT_CHECKER(obj_checker, list) \
for (vm_object_checker_t *obj_checker = (list)->objects; \
obj_checker != NULL; \
obj_checker = obj_checker->next)
/*
* Return the nth checker in the list. Aborts if n is out of range.
*/
extern vm_entry_checker_t *
checker_list_nth(checker_list_t *list, unsigned n);
/*
* Search a list of checkers for an allocation that contains the given address.
* Returns NULL if no checker contains the address.
* Returns NULL if a non-Allocation checker contains the address.
* Does not descend into submaps.
*/
extern vm_entry_checker_t *
checker_list_find_allocation(checker_list_t *list, mach_vm_address_t addr);
/*
* Search a list of checkers for a checker that contains the given address.
* May return checkers for holes.
* Returns NULL if no checker contains the address.
* Does not descend into submaps.
*/
extern vm_entry_checker_t *
checker_list_find_checker(checker_list_t *list, mach_vm_address_t addr);
/*
* Add a new vm object checker to the list.
* Aborts if the new object is null and the list already has its null object.
* Aborts if the object's ID is the same as some other object.
*/
extern void
checker_list_append_object(
checker_list_t *list,
vm_object_checker_t *obj_checker);
/*
* Return the list of entry checkers covering an address range.
* Aborts if the range includes any hole checkers.
*/
extern entry_checker_range_t
checker_list_find_range(
checker_list_t *list,
mach_vm_address_t start,
mach_vm_size_t size);
/*
* Return the list of entry checkers covering an address range.
* Hole checkers are allowed.
*/
extern entry_checker_range_t
checker_list_find_range_including_holes(
checker_list_t *list,
mach_vm_address_t start,
mach_vm_size_t size);
/*
* Like checker_list_find_range(),
* but the first and last entries are clipped to the address range.
*/
extern entry_checker_range_t
checker_list_find_and_clip(
checker_list_t *list,
mach_vm_address_t start,
mach_vm_size_t size);
/*
* Like checker_list_find_range_including_holes(),
* but the first and last entries (if any) are clipped to the address range.
*/
extern entry_checker_range_t
checker_list_find_and_clip_including_holes(
checker_list_t *list,
mach_vm_address_t start,
mach_vm_size_t size);
/*
* Attempts to simplify all entries in an address range.
*/
extern void
checker_list_simplify(
checker_list_t *list,
mach_vm_address_t start,
mach_vm_size_t size);
/*
* Replace and delete checkers in old_range
* with the checkers in new_range.
* The two ranges must have the same start address and size.
* Updates list->head and/or list->tail if necessary.
*/
extern void
checker_list_replace_range(
checker_list_t *list,
entry_checker_range_t old_range,
entry_checker_range_t new_range);
/*
* Convenience function to replace one checker with another.
* The two checkers must have the same start address and size.
*/
static inline void
checker_list_replace_checker(
checker_list_t *list,
vm_entry_checker_t *old_checker,
vm_entry_checker_t *new_checker)
{
checker_list_replace_range(list,
(entry_checker_range_t){ old_checker, old_checker },
(entry_checker_range_t){ new_checker, new_checker });
}
/*
* Convenience function to replace one checker with several checkers.
* The old and the new must have the same start address and size.
*/
static inline void
checker_list_replace_checker_with_range(
checker_list_t *list,
vm_entry_checker_t *old_checker,
entry_checker_range_t new_checkers)
{
checker_list_replace_range(list,
(entry_checker_range_t){ old_checker, old_checker },
new_checkers);
}
/*
* Remove a contiguous range of checkers from a checker list.
* The checkers are freed.
* The checkers are replaced by a new hole checker.
* VM allocations are unaffected.
*/
extern void
checker_list_free_range(
checker_list_t *list,
entry_checker_range_t range);
/* Convenience function for checker_list_remove_range() of a single checker. */
static inline void
checker_list_free_checker(
checker_list_t *list,
vm_entry_checker_t *checker)
{
checker_list_free_range(list, (entry_checker_range_t){ checker, checker });
}
/*
* Compute the end address of an entry.
* `checker->address + checker->size`, with integer overflow protection.
*/
static inline mach_vm_address_t
checker_end_address(vm_entry_checker_t *checker)
{
mach_vm_address_t end;
bool overflowed = __builtin_add_overflow(checker->address, checker->size, &end);
assert(!overflowed);
return end;
}
/*
* Return true if address is within checker's [start, end)
*/
static inline bool
checker_contains_address(vm_entry_checker_t *checker, mach_vm_address_t address)
{
return address >= checker->address && address < checker_end_address(checker);
}
/*
* Compute the share_mode value of an entry.
* This value is computed from other values in the checker and its object.
*/
extern uint8_t
checker_share_mode(
vm_entry_checker_t *checker);
/*
* Compute the is_submap value of a map entry.
*/
static inline bool
checker_is_submap(vm_entry_checker_t *checker)
{
return checker->kind == Submap;
}
/*
* Submap slide (checker_get_and_slide_submap_checkers)
*
* We want a 1:1 relationship between checkers and map entries.
* This is complicated in submaps, where the parent map's view
* of the submap uses different addresses.
*
* Our solution:
* 1. Submap content checkers store the address as if inside the submap.
* 2. When using a submap content checker in a parent map context,
* the checker is temporarily modified to use parent-relative
* addresses instead ("slide").
*
* The checker_list_t for the submap keeps track of the slide state
* of its checkers. Some places assert that the submap is or is not slid.
*
* Note that this code only deals with constant submaps; therefore
* we don't need to worry about changing checker bounds while they
* are temporarily slid.
*/
/*
* Return the nested checkers for a parent map's submap entry.
* Returns NULL if the checker is not a submap entry.
* The caller must call unslide_submap_checkers() when finished.
*/
extern checker_list_t *
checker_get_and_slide_submap_checkers(vm_entry_checker_t *checker);
/*
* Undo the effects of get_and_slide_submap_checkers().
*/
extern void
unslide_submap_checkers(checker_list_t *submap_checkers);
/*
* Convenience macro to call unslide_submap_checkers() at end of scope.
* The caller may manually unslide and then set their variable to NULL
* to cancel the automatic unslide.
*/
static inline void
cleanup_unslide_submap_checkers(checker_list_t **inout_submap_checkers)
{
if (*inout_submap_checkers) {
unslide_submap_checkers(*inout_submap_checkers);
*inout_submap_checkers = NULL;
}
}
#define DEFER_UNSLIDE \
__attribute__((cleanup(cleanup_unslide_submap_checkers)))
/*
* Adjust a start/end so that it does not extend beyond a limit.
* If start/end falls outside the limit, the output's size will
* be zero and its start will be indeterminate.
*/
extern void
clamp_start_end_to_start_end(
mach_vm_address_t * const inout_start,
mach_vm_address_t * const inout_end,
mach_vm_address_t limit_start,
mach_vm_address_t limit_end);
/*
* Adjust a address/size so that it does not extend beyond a limit.
* If address/size falls outside the limit, the output size will
* be zero and the start will be indeterminate
*/
extern void
clamp_address_size_to_address_size(
mach_vm_address_t * const inout_address,
mach_vm_size_t * const inout_size,
mach_vm_address_t limit_address,
mach_vm_size_t limit_size);
/*
* Adjust an address range so it does not extend beyond an entry's bounds.
* When clamping to a submap entry:
* checker is a submap entry in the parent map.
* address and size are in the parent map's address space on entry and on exit.
*/
extern void
clamp_address_size_to_checker(
mach_vm_address_t * const inout_address,
mach_vm_size_t * const inout_size,
vm_entry_checker_t *checker);
/*
* Adjust an address range so it does not extend beyond an entry's bounds.
* When clamping to a submap entry:
* checker is a submap entry in the parent map.
* address and size are in the parent map's address space on entry and on exit.
*/
extern void
clamp_start_end_to_checker(
mach_vm_address_t * const inout_start,
mach_vm_address_t * const inout_end,
vm_entry_checker_t *checker);
/*
* Set the VM object that an entry points to.
* Replaces any existing object. Updates self_ref_count of any objects.
*/
extern void
checker_set_object(vm_entry_checker_t *checker, vm_object_checker_t *obj_checker);
/*
* Set an entry's object to the null object.
* Identical to `checker_set_object(checker, find_object_checker_for_object_id(list, 0))`
*/
extern void
checker_set_null_object(checker_list_t *list, vm_entry_checker_t *checker);
/*
* Set an entry's object to a copy of its current object,
* with the new_object->shadow = old_object.
* The entry's current object must not be null.
*/
extern void
checker_make_shadow_object(checker_list_t *list, vm_entry_checker_t *checker);
/*
* If checker has a null VM object, change it to a new anonymous object.
*/
extern void
checker_resolve_null_vm_object(
checker_list_t *checker_list,
vm_entry_checker_t *checker);
/*
* Update an entry's checker as if a fault occurred inside it.
* Assumes that all pages in the entry were faulted.
* Aborts if the fault appears to be a copy-on-write fault; this code does
* not attempt to handle that case.
*
* - resolves null objects
* - sets the resident page count
*/
extern void
checker_fault_for_prot_not_cow(
checker_list_t *checker_list,
vm_entry_checker_t *checker,
vm_prot_t fault_prot);
/*
* Conditionally unnest one checker in a submap.
*
* submap_parent is a parent map's submap entry.
* *inout_next_address is the current address in the parent map,
* within the bounds of submap_parent.
* If the entry inside the submap that contains *inout_next_address is:
* - unallocated:
* advance *inout_next_address past the unallocated space and return NULL
* - a writeable allocation:
* unnest the appropriate range in the parent map,
* advance *inout_next_address past the unnested range,
* and return the unnested range's new checker
* - a readable allocation:
* - (unnest_readonly == false) advance past it, same as for unallocated holes
* - (unnest_readonly == true) unnest it, same as for writeable allocations
*
* Set all_overwritten = true if the newly-unnested memory will
* be promptly written to (thus resolving null objects and collapsing COW shadow chains).
*/
extern vm_entry_checker_t *
checker_list_try_unnest_one_entry_in_submap(
checker_list_t *checker_list,
vm_entry_checker_t *submap_parent,
bool unnest_readonly,
bool all_overwritten,
mach_vm_address_t * const inout_next_address);
/*
* Perform a clip-left operation on a checker, similar to vm_map_clip_left.
* Entry `right` is divided at `split`.
* Returns the new left-hand entry.
* Returns NULL if no split occurred.
* Updates list->head and/or list->tail if necessary.
*/
extern vm_entry_checker_t *
checker_clip_left(
checker_list_t *list,
vm_entry_checker_t *right,
mach_vm_address_t split);
/*
* Perform a clip-right operation on a checker, similar to vm_map_clip_right.
* Entry `left` is divided at `split`.
* Returns the new right-hand entry.
* Returns NULL if no split occurred.
* Updates list->head and/or list->tail if necessary.
*/
extern vm_entry_checker_t *
checker_clip_right(
checker_list_t *list,
vm_entry_checker_t *left,
mach_vm_address_t split);
/*
* Perform a simplify operation on a checker and the entry to its left.
* If coalescing occurs, `right` is preserved and
* the entry to the left is destroyed.
*/
extern void
checker_simplify_left(
checker_list_t *list,
vm_entry_checker_t *right);
/*
* Build a vm_checker for a newly-created memory region.
* The region is assumed to be the result of vm_allocate().
* The new checker is not linked into the list.
*/
extern vm_entry_checker_t *
make_checker_for_vm_allocate(
checker_list_t *list,
mach_vm_address_t address,
mach_vm_size_t size,
int flags_and_tag);
/*
* Create VM entries and VM entry checkers
* for the given VM entry templates.
*
* Entries will be created consecutively in contiguous memory, as specified.
* "Holes" will be deallocated during construction;
* be warned that the holes may become filled by other allocations
* including Rosetta's translations, which will cause the checker to
* fail later.
*
* Alignment handling:
* The first entry gets `alignment_mask` alignment.
* After that it is the caller's responsibility to arrange their
* templates in a way that yields the alignments they want.
*/
extern __attribute__((overloadable))
checker_list_t *
create_vm_state(
const vm_entry_template_t entry_templates[],
unsigned entry_template_count,
const vm_object_template_t object_templates[],
unsigned object_template_count,
mach_vm_size_t alignment_mask,
const char *message);
static inline __attribute__((overloadable))
checker_list_t *
create_vm_state(
const vm_entry_template_t templates[],
unsigned count,
mach_vm_size_t alignment_mask)
{
return create_vm_state(templates, count, NULL, 0,
alignment_mask, "create_vm_state");
}
/*
* Like create_vm_state, but the alignment mask defaults to PAGE_MASK
* and the template list is terminated by END_ENTRIES
*/
static inline __attribute__((overloadable))
checker_list_t *
create_vm_state(const vm_entry_template_t templates[])
{
return create_vm_state(templates, count_templates(templates), PAGE_MASK);
}
/*
* Like create_vm_state, but the alignment mask defaults to PAGE_MASK.
*/
static inline __attribute__((overloadable))
checker_list_t *
create_vm_state(const vm_entry_template_t templates[], unsigned count)
{
return create_vm_state(templates, count, PAGE_MASK);
}
/*
* Verify that the VM's state (as determined by vm_region)
* matches the expected state from a list of checkers.
*
* Returns TestSucceeded if the state is good, TestFailed otherwise.
*
* Failures are also reported as darwintest failures (typically T_FAIL)
* and failure details of expected and actual state are reported with T_LOG.
*/
extern test_result_t
verify_vm_state(checker_list_t *checker_list, const char *message);
/*
* Perform VM read and/or write faults on every page spanned by a list of checkers,
* and verify that exceptions are delivered (or not) as expected.
* This is a destructive test: the faults may change VM state (for example
* resolving COW) but the checkers are not updated.
*
* Returns TestSucceeded if the state is good, TestFailed otherwise.
*
* Failures are also reported as darwintest failures (typically T_FAIL)
* and failure details of expected and actual state are reported with T_LOG.
*/
extern test_result_t
verify_vm_faultability(
checker_list_t *checker_list,
const char *message,
bool verify_reads,
bool verify_writes);
/*
* Like verify_vm_faultability, but reads and/or writes
* from a single checker's memory.
* Returns true if the verification succeeded.
*/
extern bool
verify_checker_faultability(
vm_entry_checker_t *checker,
const char *message,
bool verify_reads,
bool verify_writes);
/*
* Like verify_checker_faultability, but reads and/or writes
* only part of a single checker's memory.
* Returns true if the verification succeeded.
*/
extern bool
verify_checker_faultability_in_address_range(
vm_entry_checker_t *checker,
const char *message,
bool verify_reads,
bool verify_writes,
mach_vm_address_t checked_address,
mach_vm_size_t checked_size);
/*
* Specification for a single trial:
* - the test's name
* - the templates for the virtual memory layout
* - the address range within that virtual memory
* layout that the tested operation should use.
*/
typedef struct vm_config_s {
char *config_name;
/*
* Test's start address is the start of the first
* entry plus start_adjustment. Test's end address
* is the end of the last entry plus end_adjustment.
* When not zero, start_adjustment is typically positive
* and end_adjustment is typically negative.
*/
mach_vm_size_t start_adjustment;
mach_vm_size_t end_adjustment;
/* First map entry gets this alignment. */
mach_vm_size_t alignment_mask;
vm_entry_template_t *entry_templates;
unsigned entry_template_count;
vm_object_template_t *object_templates;
unsigned object_template_count;
vm_entry_template_t *submap_entry_templates;
unsigned submap_entry_template_count;
vm_object_template_t *submap_object_templates;
unsigned submap_object_template_count;
} vm_config_t;
__attribute__((overloadable))
extern vm_config_t *
make_vm_config(
const char *name,
vm_entry_template_t *entry_templates,
vm_object_template_t *object_templates,
vm_entry_template_t *submap_entry_templates,
vm_object_template_t *submap_object_templates,
mach_vm_size_t start_adjustment,
mach_vm_size_t end_adjustment,
mach_vm_size_t alignment_mask);
/*
* make_vm_config() variants with fewer parameters
* (convenient for hardcoded initializer syntax)
*
* Variants that allow submap entries force submap-compatible alignment.
* Variants without submap entries use no alignment.
*/
__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
const char *name,
vm_entry_template_t *entry_templates,
vm_object_template_t *object_templates,
vm_entry_template_t *submap_entry_templates,
vm_object_template_t *submap_object_templates,
mach_vm_size_t start_adjustment,
mach_vm_size_t end_adjustment)
{
return make_vm_config(name, entry_templates, object_templates,
submap_entry_templates, submap_object_templates,
start_adjustment, end_adjustment, SUBMAP_ALIGNMENT_MASK);
}
__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
const char *name,
vm_entry_template_t *entry_templates,
vm_object_template_t *object_templates,
mach_vm_size_t start_adjustment,
mach_vm_size_t end_adjustment)
{
return make_vm_config(name, entry_templates, object_templates,
NULL, NULL,
start_adjustment, end_adjustment, 0);
}
__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
const char *name,
vm_entry_template_t *entry_templates,
mach_vm_size_t start_adjustment,
mach_vm_size_t end_adjustment)
{
return make_vm_config(name, entry_templates, NULL,
NULL, NULL,
start_adjustment, end_adjustment, 0);
}
__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
const char *name,
vm_entry_template_t *entry_templates,
vm_object_template_t *object_templates)
{
return make_vm_config(name, entry_templates, object_templates,
NULL, NULL,
0, 0, 0);
}
__attribute__((overloadable))
static inline vm_config_t *
make_vm_config(
const char *name,
vm_entry_template_t *entry_templates)
{
return make_vm_config(name, entry_templates, NULL,
NULL, NULL,
0, 0, 0);
}
/*
* Like create_vm_state, but also computes the config's desired address range.
*/
extern void
create_vm_state_from_config(
vm_config_t *config,
checker_list_t ** const out_checker_list,
mach_vm_address_t * const out_start_address,
mach_vm_address_t * const out_end_address);
/*
* Logs the contents of checkers.
* Also logs the contents of submap checkers recursively.
*/
extern void
dump_checker_range(entry_checker_range_t list);
/*
* Logs info from vm_region() for the address ranges spanned by the checkers.
* Also logs the contents of submaps recursively.
*/
extern void
dump_region_info_for_entries(entry_checker_range_t list);
/*
* Convenience functions for logging.
*/
extern const char *
name_for_entry_kind(vm_entry_template_kind_t kind);
extern const char *
name_for_kr(kern_return_t kr);
extern const char *
name_for_prot(vm_prot_t prot);
extern const char *
name_for_inherit(vm_inherit_t inheritance);
extern const char *
name_for_behavior(vm_behavior_t behavior);
extern const char *
name_for_bool(boolean_t value);
extern const char *
name_for_share_mode(uint8_t share_mode);
/* Convenience macro for compile-time array size */
#define countof(array) \
_Pragma("clang diagnostic push") \
_Pragma("clang diagnostic error \"-Wsizeof-pointer-div\"") \
(sizeof(array)/sizeof((array)[0])) \
_Pragma("clang diagnostic pop")
/* Convenience macro for a heap allocated formatted string deallocated at end of scope. */
static inline void
cleanup_cstring(char **ptr)
{
free(*ptr);
}
#define CLEANUP_CSTRING __attribute__((cleanup(cleanup_cstring)))
#define TEMP_CSTRING(str, format, ...) \
char *str CLEANUP_CSTRING; \
asprintf(&str, format, __VA_ARGS__)
/*
* Returns true if each bit set in `values` is also set in `container`.
*/
static inline bool
prot_contains_all(vm_prot_t container, vm_prot_t values)
{
return (container & values) == values;
}
/*
* Convenience functions for address arithmetic
*/
static inline mach_vm_address_t
max(mach_vm_address_t a, mach_vm_address_t b)
{
if (a > b) {
return a;
} else {
return b;
}
}
static inline mach_vm_address_t
min(mach_vm_address_t a, mach_vm_address_t b)
{
if (a < b) {
return a;
} else {
return b;
}
}
/*
* Call vm_region on an address.
* If the query address is mapped at that submap depth:
* - Sets *inout_address and *out_size to that map entry's address and size.
* [*inout_address, *inout_address + *out_size) contains the query address.
* - Sets the info from vm_region.
* - Returns true.
* If the query address is unmapped, or not mapped at that submap depth:
* - Sets *inout_address to the address of the next map entry, or ~0 if there is none.
* - Sets *out_size to zero.
* - Returns false.
*/
__attribute__((overloadable))
extern bool
get_info_for_address(
mach_vm_address_t *inout_address,
mach_vm_size_t *out_size,
vm_region_submap_info_data_64_t *out_info,
uint32_t submap_depth);
__attribute__((overloadable))
static inline bool
get_info_for_address(
mach_vm_address_t * const inout_address,
mach_vm_size_t * const out_size,
vm_region_submap_info_data_64_t * const out_info)
{
return get_info_for_address(inout_address, out_size, out_info, 0);
}
/*
* Like get_info_for_address(), but
* (1) it's faster, and
* (2) it does not get the right ref_count or shadow_depth values from vm_region.
*/
__attribute__((overloadable))
extern bool
get_info_for_address_fast(
mach_vm_address_t *inout_address,
mach_vm_size_t *out_size,
vm_region_submap_info_data_64_t *out_info,
uint32_t submap_depth);
__attribute__((overloadable))
static inline bool
get_info_for_address_fast(
mach_vm_address_t * const inout_address,
mach_vm_size_t * const out_size,
vm_region_submap_info_data_64_t * const out_info)
{
return get_info_for_address_fast(inout_address, out_size, out_info, 0);
}
/*
* Convenience function to get object_id_full from vm_region at an address.
* Returns zero if the address is mapped but has a null object.
* Aborts if the address is not mapped.
*/
extern uint64_t
get_object_id_for_address(mach_vm_address_t address);
/*
* Convenience function to get user_tag from vm_region at an address.
* Returns zero if the address is not mapped.
*/
extern uint16_t
get_user_tag_for_address(mach_vm_address_t address);
/*
* Convenience function to get user_tag from vm_region at an address,
* if that tag is within the app-specific tag range.
* Returns zero if the address is not mapped.
* Returns zero if the address's tag is not within the app-specific range
* [VM_MEMORY_APPLICATION_SPECIFIC_1, VM_MEMORY_APPLICATION_SPECIFIC_16]
*
* This is used by tests that copy user tags from nearby memory.
* The "nearby" memory might not be part of the tested range.
* Copying an arbitrary user tag from outside is undesirable
* because the VM changes some of its behavior for some tag
* values and the tests need to see consistent behavior instead.
*/
extern uint16_t
get_app_specific_user_tag_for_address(mach_vm_address_t address);
/*
* Convenience functions for vm_wire's host_priv port.
* host_priv() returns the port, or halts if it can't.
* host_priv_allowed() returns true or false.
* The host_priv port requires root on macOS.
*/
extern host_priv_t
host_priv(void);
extern bool
host_priv_allowed(void);
#endif /* VM_CONFIGURATOR_H */