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