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