1# The Read-Only Allocator 2 3Allocating read-only data in xnu. 4 5## Introduction 6 7The Read-Only Allocator is an extension of the zone allocator that facilitates 8"read-only" allocations. Data allocated from a read-only zone can only be 9modified programmatically through the `zalloc_ro_mut` function. 10 11Read-only zones are intended for very specific use cases where the data being 12managed directly affects runtime security decisions. 13 14## Discussion 15 16The purpose of the Read-Only Allocator is to protect security- 17sensitive data from being targeted by memory corruption vulnerabilities. 18 19While, historically, the modus operandi for an advanced attacker is to seize 20control of kernel execution, advances in control flow integrity defenses, such 21as PAC, means that today's attacker favors data-only attacks to achieve 22compromise. Typically this involves using a controlled write primitive to 23target data structures in the kernel's memory that effectively disables or 24bypasses obstacles standing in the way of the desired data. 25 26By necessity, we store lots of data on the heap that informs the various 27security mechanisms on our platforms. The heap traditionally dispenses 28directly mutable allocations because this fits what we need the memory for: 29frequent, fast and easy read/write access to memory. Unfortunately, these are 30also the requirements for an attacker looking to exploit a controllable write 31into kernel memory. 32 33For globals, `SECURITY_READ_ONLY_(EARLY|LATE)` provides an elegant protection 34mechanism, but unfortunately that doesn't cater for dynamic runtime 35allocations. 36 37This is where the Read-Only Allocator provides its defense: we observe that 38the majority of security-sensitive data that we allocate on the heap tends to 39be written into memory once and seldom changed thereafter. We can therefore 40trade some of this ease of access in exchange for stronger guarantees on the 41integrity of the data. 42 43Data under the control of the Read-Only Allocator can be read from just as 44cheaply and easily as other data, but writing to it must be done through the 45relatively expensive `zalloc_ro_mut` function. By insisting that data be 46written programmatically (i.e. through calling a function), we raise the cost 47of targeting that data towards the cost of seizing control of kernel 48execution. 49 50 51## Data Structure Strategies 52 53To make best use of the Read-Only Allocator, some simple advice should be 54followed: 55 561. Pointers to read-only elements should either reside in read-only memory 57 themselves, or be protected by PAC. 582. Where there is a 1:1 mapping between read/write and read-only elements, the 59 read-only element should include a pointer back to the read/write side (a 60 "back reference") that is validated when traversing from read/write to 61 read-only. 62 63On Point 1: data structures are typically stored through chains of pointers -- 64e.g. a thread points to its task, which points to its proc, which points to 65its credential. The principle here is to ensure the integrity of the entire 66chain from source pointer (e.g. thread) to destination data (e.g. credential). 67 68On Point 2: by storing a back reference on the read-only side of 1:1 69relationships, we can validate the ownership invariant that we expect to hold. 70If this is violated, it suggests that a use-after-free has happened -- perhaps 71through a genuine bug, or perhaps by an attacker targeting the zone allocator 72itself. 73 74## Should I Use the Read-Only Allocator? 75 76The Read-Only Allocator is intended to protect data from very specific 77threats. This means that for most data, it simply doesn't make sense to use 78it. Its use is primarily geared toward allocations supporting security 79boundaries such as labels, sandboxing, audit tokens, etc. 80 81 82## API 83 84Read-only zones cannot be created after lockdown. To create a new read-only 85zone, a new identifier must be added to the `zone_reserved_id_t` enumeration 86and it must be created by passing `ZC_READONLY` through either `ZONE_INIT` or 87`zone_create_ext`. 88 89We require identifiers for read-only zones for two reasons: firstly to ensure 90that we're making conscious, considered choices over which zones are made 91read-only, and secondly to allow for more stringent validation at the API 92boundary. 93 94Once a read-only zone is created, the API for using it is small and simple. 95The key functions are: 96 97- `zalloc_ro`: Allocate an element from a read-only zone. 98- `zfree_ro`: Free an element back to a read-only zone. Note that this is a 99 macro that automatically zeroes the pointer after freeing. 100- `zone_require_ro`: Verify that an element belongs to a given read-only zone 101 and panic if it doesn't. 102- `zalloc_ro_mut`: Modify part of an element allocated from a read-only zone. 103 Think of this as a special `memcpy` to write into your elements. 104- `zalloc_ro_update_elem`: A convenience function for calling `zalloc_ro_mut` 105 over the entirety of an element: simply passes an offset of zero and size 106 equal to the size of the elements in the zone. 107 108Note that `zfree_ro`, `zalloc_ro_mut` and `zalloc_ro_update_elem` will 109perform a `zone_require_ro` on the element themselves; there's no need to do 110this manually beforehand. 111