Loading...
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 | Mach IPC Security concepts ========================== This documentation aims at documenting various security concepts in this subsystem. Each section covers a single concept, and will cover topics such as motivation, design of the feature, and implementation details that are important. ## IPC space policy ### Motivation and design Over time our IPC policies have grown in complexity and depend on several parameters, to name a few: being a simulated process, a platform binary, or having browser entitlements. As a result, a notion of IPC space policy exists that projects all various system policies into a single enum per IPC space. This policy is an inherent immutable property of an IPC space which allows to query its value without holding any locks. ### Implementation details The source of truth for IPC policies is the `struct ipc_space::is_policy` field, which can be accessed with the `ipc_space_policy()` accessor. This field is computed when a task IPC space is enabled (in `ipc_task_enable()`), and is immutable for the lifetime of this space. In addition to that, the field is dPACed in order to be resilient to early memory corruption primitives. For conveniency reasons, the policy bits of a space are injected in other enums (such as `mach_msg_options64_t`). The `IPC_SPACE_POLICY_BASE()` macro helps forming types that extend the space policy. ## Pinned Entries ### Motivation and design Certain kinds of send rights have a well understood lifecycle on the system, during which there must always be an extent send right alive for the port. Obvious examples of this are task or thread control ports which must have a live send right in their corresponding IPC space while the task or thread they reference is alive. In order to catch port management issues that could lead to various confused deputies issues, the Mach IPC subsystem provides a notion of pinned send rights. Pinned send rights is a concept of the Mach IPC Entry, which denotes that this entry must always have at least one extent send right alive. Pinning can be undone in two ways: - when a port receive right is destroyed, pinning is no longer effective, and entries will be automatically unpinned as part of the dead-name check; - unpinning can be explicitly requested by the kernel. ### When and how to used pinned rights? Pinned rights were designed to protect `mach_task_self()` and `_pthread_mach_thread_self_direct()` which can lead to grave security bugs when port lifecycle management mistakes are made. The bracketing there is very simple: - task ports are never unpinned; - thread ports are unpinned when the thread terminates. There might be other ports on the system which can use this facility, however they must have the right shape: either the port dying (the receive right being destroyed) is an adequate way to unpin the entry, or there must be a clearly identified kernel path that can unpin the entry without any confusion with other ports. Adding unpinning paths that can't verify that the port being unpinned is "theirs" would lead to weakening this feature and would reintroduce avenues to confuse the system due to port mismanagement bugs. ### Implementation details Pinning is denoted by the `IE_BITS_PINNED_SEND` bit of the `struct ipc_entry::ie_bits` field. IPC entries gain this bit the first time the kernel calls `ipc_port_copyout_send_pinned()` for a given port and IPC space. When the `IE_BITS_PINNED_SEND` is set, then the `MACH_PORT_TYPE_SEND` bit must be set too, with the `IE_BITS_UREFS()` for this entry being at least 1. In order to respect that pinning is ignored immediately when a port becomes dead, enforcing `IE_BITS_PINNED_SEND` semantics must be done under the space lock, either right after a dead-name conversion check happened (`ipc_right_check()` has been called) or by checking explicitly that the port is still active (`ip_active()` returns true) when a dead-name conversion isn't desirable. ### Usage and enforcement Task and thread control ports are pinned for all processes within the owning IPC space of the task in question, for all processes on the system. The `ipc_control_port_options` boot-arg determines the reaction of the system to violations of pinning: - hardened processes and above have hard enforcement of pinning rules (violating the rules terminates the process); - other processes have a soft enforcement: violating pinning rules returns a `KERN_INVALID_CAPABILITY` error and generates a non fatal guard exception. |