macOS MACF - Mandatory Access Control Framework
Basic Information
MACF stands for Mandatory Access Control Framework, which is a security system built into the operating system to help protect your computer. It works by setting strict rules about who or what can access certain parts of the system, such as files, applications, and system resources. By enforcing these rules automatically, MACF ensures that only authorized users and processes can perform specific actions, reducing the risk of unauthorized access or malicious activities.
Note that MACF doesn't really make any decisions as it just intercepts actions, it leaves the decisions to the policy modules (kernel extensions) it calls like AppleMobileFileIntegrity.kext
, Quarantine.kext
, Sandbox.kext
, TMSafetyNet.kext
and mcxalr.kext
.
Flow
Process performs a syscall/mach trap
The relevant function is called inside the kernel
Function calls MACF
MACF checks policy modules that requested to hook that function in their policy
MACF calls the relevant policies
Policies indicates if they allow or deny the action
Apple is the only one that can use the MAC Framework KPI.
Labels
MACF use labels that then the policies checking if they should grant some access or not will use. The code of the labels struct declaration can be found here, which is then used inside the struct ucred
in here in the cr_label
part. The label contains flags and s number of slots that can be used by MACF policies to allocate pointers. For example Sanbox will point to the container profile
MACF Policies
A MACF Policy defined rule and conditions to be applied in certain kernel operations.
A kernel extension could configure a mac_policy_conf
struct and then register it calling mac_policy_register
. From here:
It's easy to identify the kernel extensions configuring these policies by checking calls to mac_policy_register
. Moreover, checking the disassemble of the extension it's also possible to find the used mac_policy_conf
struct.
Note that MACF policies can be registered and unregistered also dynamically.
One of the main fields of the mac_policy_conf
is the mpc_ops
. This fied specifies which opreations the policy is interested in. Note that there are hundres of them, so it's possible to zero all of them and then select just the ones the policy is interested on. From here:
Almost all the hooks will be called back by MACF when one of those operations are intercepted. However, mpo_policy_*
hooks are an exception because mpo_hook_policy_init()
is a callback called upon registration (so after mac_policy_register()
) and mpo_hook_policy_initbsd()
is called during late registration once the BSD subsystem has initialised properly.
Moreover, the mpo_policy_syscall
hook can be registered by any kext to expose a private ioctl style call interface. Then, a user client will be able to call mac_syscall
(#381) specifying as parameters the policy name with an integer code and optional arguments.
For example, the Sandbox.kext
uses this a lot.
Checking the kext's __DATA.__const*
is possible to identify the mac_policy_ops
structure used when registering the policy. It's possible to find it because its pointer is at an offset inside mpo_policy_conf
and also because the amount of NULL pointers that will be in that area.
Moreover, it's also possible to get the list of kexts that have configured a policy by dumping from memory the struct _mac_policy_list
which is updated with every policy that is registered.
MACF Initialization
MACF is initialised very soon. It's set up in XNU's bootstrap_thread
: after ipc_bootstrap
a call to mac_policy_init()
which initializes the mac_policy_list
and moments later mac_policy_initmach()
is called. Among other things, this function will get all the Apple kexts with the AppleSecurityExtension
key in their Info.plist like ALF.kext
, AppleMobileFileIntegrity.kext
, Quarantine.kext
, Sandbox.kext
and TMSafetyNet.kext
and loads them.
MACF Callouts
It's common to find callouts to MACF defined in code like: #if CONFIG_MAC
conditional blocks. Moreover, inside these blocks it's possible to find calls to mac_proc_check*
which calls MACF to check for permissions to perform certain actions. Moreover, the format of the MACF callouts is: mac_<object>_<opType>_opName
.
The object is one of the following: bpfdesc
, cred
, file
, proc
, vnode
, mount
, devfs
, ifnet
, inpcb
, mbuf
, ipq
, pipe
, sysv[msg/msq/shm/sem]
, posix[shm/sem]
, socket
, kext
.
The opType
is usually check which will be used to allow or deny the action. However, it's also possible to find notify
, which will allow the kext to react to the given action.
You can find an example in https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/bsd/kern/kern_mman.c#L621:
Then, it's possible to find the code of mac_file_check_mmap
in https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_file.c#L174
Which is calling the MAC_CHECK
macro, whose code can be found in https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_internal.h#L261
Which will go over all the registered mac policies calling their functions and storing the output inside the error variable, which will only be overridable by mac_error_select
by success codes so if any check fails the complete check will fail and the action won't be allowed.
However, remember that not all MACF callouts are used only to deny actions. For example, mac_priv_grant
calls the macro MAC_GRANT, which will grant the requested privilege if any policy answers with a 0:
priv_check & priv_grant
These callas are meant to check and provide (tens of) privileges defined in bsd/sys/priv.h.
Some kernel code would call priv_check_cred()
from bsd/kern/kern_priv.c with the KAuth credentials of the process and one of the privileges code which will call mac_priv_check
to see if any policy denies giving the privilege and then it calls mac_priv_grant
to see if any policy grants the privilege
.
proc_check_syscall_unix
This hook allows to intercept all system calls. In bsd/dev/[i386|arm]/systemcalls.c
it's possible to see the declared function unix_syscall
, which contains this code:
Which will check in the calling process bitmask if the current syscall should call mac_proc_check_syscall_unix
. This is because syscalls are called so frequently that it's interesting to avoid calling mac_proc_check_syscall_unix
every time.
Note that the function proc_set_syscall_filter_mask()
, which set the bitmask syscalls in a process is called by Sandbox to set masks on sandboxed processes.
Exposed MACF syscalls
It's possible to interact with MACF through some syscalls defined in security/mac.h:
References
Last updated