xref: /xnu-12377.41.6/doc/mach_ipc/kmsg.md (revision bbb1b6f9e71b8cdde6e5cd6f4841f207dee3d828)
1IPC kmsg
2========
3
4IPC kmsg is the kernel representation of an in flight Mach IPC
5message.
6
7## Layouts
8
9IPC kmsg have a complex in memory layout that is designed to separate kernel
10pointers from user controled data. There are 4 layouts that we'll present in
11this section.
12
13
14### Mach messages
15
16IPC kmsg is meant to wrap a Mach message, which is made of 4 different parts,
17some of them optional:
18- a header (`mach_msg_header_t`) is always present;
19- if `msgh_bits` has the `MACH_MSGH_BITS_COMPLEX` bit set, then a descriptor
20  count and array of descriptors follows;
21- then a body of bytes follows to pad the message up to `msgdh_size` bytes,
22  (and can potentially be empty);
23- lastly, a free floating "aux" data bag can carry ancilary data next to the
24  message (it is used by libdispatch to carry over things like activity IDs
25  for logging).
26
27```
28           ╭ ┌─[ mach_msg_header_t ]───────────────────────────────────┐ ╮
29           │ │ msgh_bits                   (if MACH_MSGH_BITS_COMPLEX) │ │
30           │ │ msgh_size                                ╷              │ │
31           │ │ msgh_remote_port                         │              │ │
32           │ │ msgh_local_port                          │              │ │
33           │ │ msgh_voucher_port                        │              │ │
34           │ │ msgh_id                                  ▼              │ │
35           │ ├────────────────────────────┬────────────────────────────┤ │ contains
36           │ │ body (pure data)           │ msgh_descriptor_count (dc) │ │ pointers
37           │ ╎                            ├────────────────────────────┤ │ in kernel
38           │ ╎                            │ descriptor #1              │ │
39 msgh_size │ ╎                            │                            │ │
40   bytes   │ ╎                            ├────────────────────────────┤ │
41           │ ╎                            ╎                            ╎ │
42           │ ╎                            ╎                            ╎ │
43           │ ╎                            ├────────────────────────────┤ │
44           │ ╎                            │ descriptor #dc             │ │
45           │ ╎                            │                            │ │
46           │ ╎                            ├────────────────────────────┤ ╯
47           │ ╎                            │ body (pure data)           │
48           │ ╎                            ╎                            ╎
49           │ ╎                            ╎                            ╎
50           │ │                            │                            │
51           ╰ ├────────────────────────────┴────────────────────────────┤
52             │ trailer (various sizes)                                 │
53             │                                                         │
54             │                                                         │
55             │                                                         │
56             │                                                         │
57             │                                                         │
58             └─────────────────────────────────────────────────────────┘
59
60           ╭ ┌─[ mach_msg_aux_header_t ]───────────────────────────────┐
61           │ │ msgdh_size                                              │
62           │ │ msgdh_reserved                                          │
63           │ ├─────────────────────────────────────────────────────────┤
64msgdh_size │ │ payload (pure data)                                     │
65           │ ╎                                                         ╎
66           │ ╎                                                         ╎
67           │ │                                                         │
68           ╰ └─────────────────────────────────────────────────────────┘
69```
70
71Note that subsystems like MIG used to assume that the entire Mach message
72from header to trailer was in contiguous memory, but that is no longer true,
73and a Mach message in the kernel can be split in 3 separate non contiguous
74parts:
75- its header + descriptors (which contain kernel pointers in kernel);
76- its pure data body and trailer;
77- its auxiliary data payload.
78
79### The core IPC kmsg type: `struct ipc_kmsg`
80
81As far as layout is concerned, an IPC kernel message is made of two halves: some
82fields are always used and make up the "header" of the kmsg, and then some data
83follows that is used in various ways depending on the `ikm_type` field of the
84message.
85
86```
87             ┌─────────────────────────────────────────────────────────┐ ╮
88             │                                                         │ │
89             │                  ... header fields ...                  │ │
90             │                                                         │ │
91             ├─────────────────────────────────────────────────────────┤ │
92             │ ikm_aux_size                                            │ │
93             ├─────────────────────────────────────────────────────────┤ │ "header"
94             │                                                         │ │
95             │               ... more header fields ...                │ │
96             │                                                         │ │
97             ├─────────────────────────────────────────────────────────┤ │
98             │ ikm_type                                                │ │
99             ├────────────────────────────┬────────────────────────────┤ ┤
100             │ ikm_big_data               │ ikm_small_data             │ │
101             │                            │                            │ │
102             │                            │                            │ │
103             │                            │                            │ │
104             │                            │                            │ │
105             │                            │                            │ │
106             │                            │                            │ │  "body"
107             │                            │                            │ │
108             │                            │                            │ │
109             │                            ├────────────────────────────┤ │
110             │                            │ ikm_kdata                  │ │
111             │                            │ ikm_udata                  │ │
112             │                            │ ikm_kdata_size             │ │
113             │                            │ ikm_udata_size             │ │
114             └────────────────────────────┴────────────────────────────┘ ╯
115```
116
117This data structure has 4 configurations, depending on the value of `ikm_type`,
118detailed below.
119
120### `IKM_TYPE_ALL_INLINED`: no external allocation
121
122For this kmsg type, there is no external allocation, and the `ikm_big_data`
123inline buffer of the kmsg is used to fit all parts of the mach message this way:
124
125```
126             ┌─────────────────────────────────────────────────────────┐
127             │                                                         │
128             │                  ... header fields ...                  │
129             │                                                         │
130             ├─────────────────────────────────────────────────────────┤
131        ╭────┼ ikm_aux_size                                            │
132        │    ├─────────────────────────────────────────────────────────┤
133        │    │                                                         │
134        │    │               ... more header fields ...                │
135        │    │                                                         │
136        │    ├─────────────────────────────────────────────────────────┤
137        │    │ ikm_type                         (IKM_TYPE_ALL_INLINED) │
138        │    ├─┬─────────────────────────────────────────────────────┬─┤
139        │    │ │                                                     │ │
140        │    │ │                                                     │ │
141        │    │ │          Mach message header + descriptors          │ │
142        │    │ │                                                     │ │
143        │    │ │                                                     │ │
144        │    │ ├─────────────────────────────────────────────────────┤ │
145        │    │ │                                                     │ │
146        │    │ │                                                     │ │
147        │    │ │                                                     │ │
148        │    │ │             Mach message body + trailer             │ │
149        │    │ │                                                     │ │
150        │    │ │                                                     │ │
151        │    │ │                                                     │ │
152        │    │ └─────────────────────────────────────────────────────┘ │
153        │    │                                                         │
154        │    │                     (unused space)                      │
155        │    │                                                         │
156        │  ╭ │ ┌─────────────────────────────────────────────────────┐ │
157        │  │ │ │                                                     │ │
158        ╰─▶│ │ │           Mach message auxiliary payload            │ │
159           │ │ │                                                     │ │
160           ╰ └─┴─────────────────────────────────────────────────────┴─┘
161```
162
163### `IKM_TYPE_UDATA_OOL`: external allocation for pure data
164
165In this layout, the "pure" data of the Mach message (its body, trailer, and
166auxiliary payload) is allocated out of line. The kmsg uses its `ikm_small_data`
167inline buffer to store the Mach message header and descriptors, this way:
168
169```
170             ┌─────────────────────────────────────────────────────────┐
171             │                                                         │
172             │                  ... header fields ...                  │
173             │                                                         │
174             ├─────────────────────────────────────────────────────────┤
175             │ ikm_aux_size                                            ┼────╮
176             ├─────────────────────────────────────────────────────────┤    │
177             │                                                         │    │
178             │               ... more header fields ...                │    │
179             │                                                         │    │
180             ├─────────────────────────────────────────────────────────┤    │
181             │ ikm_type                           (IKM_TYPE_UDATA_OOL) │    │
182           ╭ ├─┬─────────────────────────────────────────────────────┬─┤◀─╮ │
183           │ │ │                                                     │ │  │ │
184           │ │ │                                                     │ │  │ │
185        ╭─▶│ │ │          Mach message header + descriptors          │ │  │ │
186        │  │ │ │                                                     │ │  │ │
187        │  │ │ │                                                     │ │  │ │
188        │  ╰ │ └─────────────────────────────────────────────────────┘ │  │ │
189        │    │                                                         │  │ │
190        │    │                     (unused space)                      │  │ │
191        │    │                                                         │  │ │
192        │    ├─────────────────────────────────────────────────────────┤  │ │
193        │    │ ikm_kdata                                              ─┼──╯ │
194        │    │ ikm_udata                                              ─┼──╮ │
195        ╰────┼ ikm_kdata_size                                          │  │ │
196        ╭────┼ ikm_udata_size                                          │  │ │
197        │    └─────────────────────────────────────────────────────────┘  │ │
198        │                                                                 │ │
199        │                                 ╭───────────────────────────────╯ │
200        │                                 │                                 │
201        │  ╭ ┌────────────────────────────▼────────────────────────────┐    │
202        │  │ │                                                         │    │
203        │  │ │                                                         │    │
204        │  │ │                                                         │    │
205        │  │ │               Mach message body + trailer               │    │
206        │  │ │                                                         │    │
207        │  │ │                                                         │    │
208        ╰─▶│ │                                                         │    │
209           │ ├─────────────────────────────────────────────────────────┤    │
210           │ │                     (unused space)                      │    │
211           │ ├─────────────────────────────────────────────────────────┤ ╮  │
212           │ │                                                         │ │  │
213           │ │             Mach message auxiliary payload              │ │◀─╯
214           │ │                                                         │ │
215           ╰ └─────────────────────────────────────────────────────────┘ ╯
216```
217
218Note that there can be unused space in the pure data body due to the fact that
219the size of user and kernel descriptors aren't the same and the kernel has to
220anticipate for the "worse" size change possible.
221
222### `IKM_TYPE_KDATA_OOL`: legacy linear layout
223
224In this layout, the entire Mach message is allocated out of line in a single
225linear allocation. This is a legacy representation that is now only used for
226DriverKit replies, with a fixed size of 0x17c0 bytes. Note that because of this,
227there is never auxiliary data in this form.
228
229This layout should no longer be used otherwise, as it mixes kernel pointers and
230user controlled data in the same allocation.
231
232```
233             ┌─────────────────────────────────────────────────────────┐
234             │                                                         │
235             │                  ... header fields ...                  │
236             │                                                         │
237             ├─────────────────────────────────────────────────────────┤
238             │ ikm_aux_size                                        (0) │
239             ├─────────────────────────────────────────────────────────┤
240             │                                                         │
241             │               ... more header fields ...                │
242             │                                                         │
243             ├─────────────────────────────────────────────────────────┤
244             │ ikm_type                           (IKM_TYPE_KDATA_OOL) │
245             ├─────────────────────────────────────────────────────────┤
246             │                                                         │
247             │                                                         │
248             │                                                         │
249             │                                                         │
250             │                     (unused space)                      │
251             │                                                         │
252             │                                                         │
253             │                                                         │
254             │                                                         │
255             ├─────────────────────────────────────────────────────────┤
256             │ ikm_kdata                                              ─┼──╮
257             │ ikm_udata                                        (NULL) │  │
258        ╭────┼ ikm_kdata_size                                          │  │
259        │    │ ikm_udata_size                                      (0) │  │
260        │    └─────────────────────────────────────────────────────────┘  │
261        │                                                                 │
262        │                                 ╭───────────────────────────────╯
263        │                                 │
264        │  ╭ ┌────────────────────────────▼────────────────────────────┐
265        │  │ │                                                         │
266        │  │ │                                                         │
267        │  │ │            Mach message header + descriptors            │
268        │  │ │                                                         │
269        │  │ │                                                         │
270        │  │ ├─────────────────────────────────────────────────────────┤
271        │  │ │                                                         │
272        │  │ │                                                         │
273        │  │ │                                                         │
274        ╰─▶│ │               Mach message body + trailer               │
275           │ │                                                         │
276           │ │                                                         │
277           │ │                                                         │
278           │ ├─────────────────────────────────────────────────────────┤
279           │ │                     (unused space)                      │
280           ╰ └─────────────────────────────────────────────────────────┘
281```
282
283### `IKM_TYPE_ALL_OOL`: external allocations for both kernel data and pure data
284
285In this layout, the "pure" data of the Mach message (its body, trailer, and
286auxiliary payload) is allocated out of line like for the `IKM_TYPE_UDATA_OOL`
287layout, however unlike this layout, the Mach message header and descriptors are
288also allocated out of line, this way:
289
290```
291             ┌─────────────────────────────────────────────────────────┐
292             │                                                         │
293             │                  ... header fields ...                  │
294             │                                                         │
295             ├─────────────────────────────────────────────────────────┤
296             │ ikm_aux_size                                            ┼─────╮
297             ├─────────────────────────────────────────────────────────┤     │
298             │                                                         │     │
299             │               ... more header fields ...                │     │
300             │                                                         │     │
301             ├─────────────────────────────────────────────────────────┤     │
302             │ ikm_type                             (IKM_TYPE_ALL_OOL) │     │
303             ├─────────────────────────────────────────────────────────┤     │
304             │                                                         │     │
305             │                                                         │     │
306             │                                                         │     │
307             │                                                         │     │
308             │                     (unused space)                      │     │
309             │                                                         │     │
310             │                                                         │     │
311             │                                                         │     │
312             │                                                         │     │
313             ├─────────────────────────────────────────────────────────┤     │
314             │ ikm_kdata                                              ─┼─╮   │
315             │ ikm_udata                                              ─┼─┼─╮ │
316        ╭────┼ ikm_kdata_size                                          │ │ │ │
317      ╭─┼────┼ ikm_udata_size                                          │ │ │ │
318      │ │    └─────────────────────────────────────────────────────────┘ │ │ │
319      │ │                                                                │ │ │
320      │ │                                 ╭──────────────────────────────╯ │ │
321      │ │                                 │                                │ │
322      │ │  ╭ ┌────────────────────────────▼────────────────────────────┐   │ │
323      │ │  │ │                                                         │   │ │
324      │ │  │ │                                                         │   │ │
325      │ ╰─▶│ │            Mach message header + descriptors            │   │ │
326      │    │ │                                                         │   │ │
327      │    │ │                                                         │   │ │
328      │    ╰ └─────────────────────────────────────────────────────────┘   │ │
329      │                                                                    │ │
330      │                                   ╭────────────────────────────────╯ │
331      │                                   │                                  │
332      │    ╭ ┌────────────────────────────▼────────────────────────────┐     │
333      │    │ │                                                         │     │
334      │    │ │                                                         │     │
335      │    │ │                                                         │     │
336      │    │ │               Mach message body + trailer               │     │
337      │    │ │                                                         │     │
338      │    │ │                                                         │     │
339      ╰───▶│ │                                                         │     │
340           │ ├─────────────────────────────────────────────────────────┤     │
341           │ │                     (unused space)                      │     │
342           │ ├─────────────────────────────────────────────────────────┤ ╮   │
343           │ │                                                         │ │   │
344           │ │             Mach message auxiliary payload              │ │◀──╯
345           │ │                                                         │ │
346           ╰ └─────────────────────────────────────────────────────────┘ ╯
347```
348
349Note that there can be unused space in the pure data body due to the fact that
350the size of user and kernel descriptors aren't the same and the kernel has to
351anticipate for the "worse" size change possible.
352
353## Signing
354
355IPC Kmsg have been the bread and butter of kernel exploitation for a decade,
356due to how reachable it is, and how flexible its state machine is.
357
358In order to reduce how appetizing this is, IPC kmsg inside of the kernel
359use PAC in order to check the integrity of kernel messages.
360
361### Descriptor signing (Core XNU)
362
363Inline descriptors are very interesting because an attacker can control their
364count, content, and disposition at will, which lets them reach a fairly large
365amount of primitives.
366
367While messages are in flight in the kernel, the descriptors contain pointers
368to various kernel objects. This has been a target of choice for attackers to
369improve their early primitives.
370
371In order to make descriptor pointers unattractive, XNU now signs these
372descriptors inside the kernel anywhere it uses descriptors, by expanding
373the types to form a union between:
374- a user type prefixed with `u_`,
375- an unsigned pointer variant prefixed with `kext_`,
376- a signed pointer (keeping its current name).
377
378For example, here is how a port descriptor type looks like in kernel:
379
380```c
381typedef struct {
382        union {
383                mach_port_t           __ipc_desc_sign("port") name;
384                mach_port_t           kext_name;
385                mach_port_t           u_name;
386        };
387        unsigned int                  pad2 : 16;
388        mach_msg_type_name_t          disposition : 8;
389        mach_msg_descriptor_type_t    type : 8;
390        uint32_t                      pad_end;
391} mach_msg_port_descriptor_t;
392```
393
394resulting in this type layout:
395
396```
3970x0000,[  0x10] (struct mach_msg_port_descriptor_t)) {
398    0x0000,[   0x8] (anonymous union)) {
399        0x0000,[   0x8] (__ptrauth(2,1,427) mach_port_t) name
400        0x0000,[   0x8] (mach_port_t) kext_name
401        0x0000,[   0x8] (mach_port_t) u_name
402    }
403    0x0008,[   0x4] (unsigned int : 0x10) pad2
404    0x000a,[   0x4] (mach_msg_type_name_t : 0x08) disposition
405    0x000b,[   0x4] (mach_msg_descriptor_type_t : 0x08) type
406    0x000c,[   0x4] (uint32_t) pad_end
407}
408```
409
410### Descriptor signing (kernel extensions)
411
412On macOS where kernel extensions exist and use the arm64e slice,
413the ABI is already set and signing kernel pointers would be an ABI break.
414
415Fortunately, IPC kmsgs are not ABI, and the way kernel extensions
416interact with Mach is via three calls:
417- `mach_msg_send_from_kernel` to perform "one way" messaging,
418- `mach_msg_rpc_from_kernel` to perform query/reply messaging,
419- `mach_msg_destroy_from_kernel` to dispose of the rights in a message buffer.
420
421In order to hide the PAC ABI that XNU uses, these 3 entry points are specialized
422for kernel extensions (using symbol names ending in `_proper` for historical
423reasons). These entry points propagate that the caller is a kernel extension
424to the `ipc_kmsg_get_from_kernel()` or `ipc_kmsg_put_to_kernel()` functions
425who are responsible for moving message data between the kernel extension
426provided buffers and the `ipc_kmsg_t` structures.
427
428These kext buffers tend to be short lived, which means that the vast majority
429of in flight messages have signed descriptors at rest.
430
431### Header/trailer signing
432
433Unlike descriptors which actually do not really tend to be manipulated by code
434a lot but more used as a serialization format, `mach_msg_header_t` is used
435pervasively, (mem)copied around, and a very well established ABI. Signing
436its port pointers would be extremely desirable, but also an ABI nightmare.
437
438Instead, the header (and trailer) are signed with gPAC as soon as headers
439are formed/copied-in. This signature covers the message descriptor count,
440and is diversified with the kmsg address.
441
442When a message is about to be used to return information to userspace in any
443shape or form, the signature is being validated and the descriptor count
444of the message returned as a side effect of checking the signature. This
445descriptor count is then used as the source of truth for indexing in
446descriptors, which should dramatically reduce tampering risks.
447
448