/home

iOS Sandbox Internals - Part I

Introduction

Apple’s sandbox implementation, named “SeatBelt”, plays a crucial role in their devices’ security model. The goal of this post is to offer you a clearer view on how it works on iOS. This first part will be dedicated to the MACF hooking mechanism. Later posts will cover other subjects such as the sandbox profile binary format, sandbox extensions…

As you may know, when an app is installed on an iOS device, two containers are automatically created for it. Containers are essentially directories; one is located at /private/var/containers/Bundle/Application/<UUID>/ and contains the application itself (bundle container). The other one is located at /private/var/mobile/Containers/Data/Application/<UUID>/ and contains the application’s data (data container).

Bundle container structure

1
2
3
4
5
6
7
/private/var/containers/Bundle/Application/<UUID>/
drwxr-xr-x  3 _installd _installd  192 Aug 16 16:55 ./
drwxr-xr-x 39 _installd _installd 1.3K Aug 16 16:55 ../
-rw-r--r--  1 root      _installd  525 Aug 16 16:55 .com.apple.mobile_container_manager.metadata.plist
-rw-r--r--  1 _installd wheel      685 Aug 16 16:55 BundleMetadata.plist
drwxr-xr-x 55 _installd _installd 4.5K Aug 16 16:55 Signal.app/
-rw-r--r--  1 _installd wheel     1.7K Aug 16 16:55 iTunesMetadata.plist

Data container structure

1
2
3
4
5
6
7
8
9
/private/var/mobile/Containers/Data/Application/<UUID>/
drwxr-xr-x   7 mobile mobile  256 Aug 16 16:55 ./
drwxr-xr-x 213 mobile mobile 6.7K Aug 16 16:55 ../
-rw-r--r--   1 root   mobile  588 Aug 16 16:55 .com.apple.mobile_container_manager.metadata.plist
drwxr-xr-x   2 mobile mobile   64 Aug 16 16:55 Documents/
drwxr-xr-x   6 mobile mobile  192 Aug 16 17:21 Library/
drwxr-xr-x   2 mobile mobile   96 Aug 16 16:55 StoreKit/
drwxr-xr-x   2 mobile mobile   64 Aug 16 16:55 SystemData/
drwxr-xr-x   4 mobile mobile  128 Aug 16 16:55 tmp/

But how can Apple restrict an app from performing certain operations (opening files it shouldn’t for example) ?

The MACF hooking mechanism

The sandbox is mandatory for all third party applications, it allows the system to filter what each process is able to do. It is implemented at kernel level as Mandatory Access Control Framework (MACF) hooks. MACF’s role is to intercept critical operations an app may want to perform, before handing it to policies that define whether or not the application is allowed to do what is wants.

Multiple policies can live together, a single hook can be implemented by multiple policies, in which case the different callbacks would all be called successively. Some of the most notorious security policies are the Apple Mobile File Integrity (AMFI) and SeatBelt, which we will talk about.

Registering MACF policies

We will now see how Apple declares sandbox policies and how those are registered inside the kernel. We will take a look at the structures defining the policies and how they map MAC operations to the corresponding policies’ s hooks (while focusing on SeatBelt).

To do so, we will use a symbolicated kernel cache from iOS 26.3.

Policies are registered through kernel extensions, such as the com.apple.security.sandbox extension by using entry points.

Entry points are operations the policy modules can hook into, they are usually built the same way, by starting with mpo_ and specifying an object and an operation (example: mpo_vnode_check_open).

The hooks are predefined points enforcing security policies that can then trigger policy callbacks. Callbacks are functions bound to entry points that define whether the operation should be allowed or denied.

Opening the com.apple.security.sandbox allows us to see several hooks (I’m using Binary Ninja’s kernel cache triage for this):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
_hook_vnode_notify_setflags
_hook_vnode_check_unlink
_hook_vnode_check_swap
_hook_vnode_check_rename
_hook_vnode_check_exec
_hook_vnode_check_create
_hook_vnode_check_clone
_hook_vnode_notify_rename
_hook_thread_userret
_hook_system_check_sysctlbyname
_hook_pty_notify_grant
_hook_proc_notify_exec_complete
_hook_proc_check_get_task_with_flavor
_hook_policy_syscall
_hook_policy_init
_hook_mount_check_mount_late
_hook_cred_label_update_execve 

Sandbox policies are defined through the mac_policy_conf structure, by specifying a policy name, a full name, and a mac_policy_ops structure containing the different operations. Searching for mac_policy xrefs leads us to the _kmod_start() function:

_kmod_start

Here is the declaration of the mac_policy_register function:

1
2
//xnu/security/mac_policy.h
int mac_policy_register(struct mac_policy_conf *mpc, mac_policy_handle_t *handlep, void *xd);

Here is how mac_policy_conf is defined:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
//xnu/security/mac_policy.h
struct mac_policy_conf {
	const char              *mpc_name;              
	const char              *mpc_fullname;          
	char const * const *mpc_labelnames;             
	unsigned int             mpc_labelname_count;   
	const struct mac_policy_ops *mpc_ops; // operation vector
	int                      mpc_loadtime_flags;    
	int                     *mpc_field_off;         
	int                      mpc_runtime_flags;     
	mpc_t                    mpc_list;              
	void                    *mpc_data;              
};

#define mpc_t   struct mac_policy_conf *
// Note: I've deleted some comments for clarity purposes

Defining the struct inside of our disassembler, allows us to have a clean representation of how the SeatBelt sandbox policy is registered:

_kmod_start

By reconstructing the mac_policy_ops structure (see definition), we can identify callbacks that were not symbolicated.

mac_policy_ops
  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
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
typedef void (*mac_hook_t)(void);

struct mac_policy_ops {
	mac_hook_t mpo_audit_check_postselect;
	mac_hook_t mpo_audit_check_preselect;

	mac_hook_t mpo_graft_check_graft;
	mac_hook_t mpo_graft_check_ungraft;
	mac_hook_t mpo_graft_notify_graft;
	mac_hook_t mpo_graft_notify_ungraft;

	mac_hook_t mpo_cred_check_label_update_execve;
	mac_hook_t mpo_cred_check_label_update;
	mac_hook_t mpo_cred_check_visible;
	mac_hook_t mpo_cred_label_associate_fork;
	mac_hook_t mpo_cred_label_associate_kernel;
	mac_hook_t mpo_cred_label_associate;
	mac_hook_t mpo_cred_label_associate_user;
	mac_hook_t mpo_cred_label_destroy;
	mac_hook_t mpo_cred_label_externalize_audit;
	mac_hook_t mpo_cred_label_externalize;
	mac_hook_t mpo_cred_label_init;
	mac_hook_t mpo_cred_label_internalize;
	mac_hook_t mpo_cred_label_update_execve;
	mac_hook_t mpo_cred_label_update;

	mac_hook_t mpo_devfs_label_associate_device;
	mac_hook_t mpo_devfs_label_associate_directory;
	mac_hook_t mpo_devfs_label_copy;
	mac_hook_t mpo_devfs_label_destroy;
	mac_hook_t mpo_devfs_label_init;
	mac_hook_t mpo_devfs_label_update;

	mac_hook_t mpo_file_check_change_offset;
	mac_hook_t mpo_file_check_create;
	mac_hook_t mpo_file_check_dup;
	mac_hook_t mpo_file_check_fcntl;
	mac_hook_t mpo_file_check_get_offset;
	mac_hook_t mpo_file_check_get;
	mac_hook_t mpo_file_check_inherit;
	mac_hook_t mpo_file_check_ioctl;
	mac_hook_t mpo_file_check_lock;
	mac_hook_t mpo_file_check_mmap_downgrade;
	mac_hook_t mpo_file_check_mmap;
	mac_hook_t mpo_file_check_receive;
	mac_hook_t mpo_file_check_set;
	mac_hook_t mpo_file_label_init;       /* deprecated not called anymore */
	mac_hook_t mpo_file_label_destroy;    /* deprecated not called anymore */
	mac_hook_t mpo_file_label_associate;  /* deprecated not called anymore */
	mac_hook_t mpo_file_notify_close;

	mac_hook_t mpo_proc_check_launch_constraints;
	mac_hook_t mpo_proc_notify_service_port_derive;
	mac_hook_t *mpo_proc_check_set_task_exception_port;
	mac_hook_t  *mpo_proc_check_set_thread_exception_port;

	mac_hook_t mpo_reserved08;
	mac_hook_t mpo_reserved09;
	mac_hook_t mpo_reserved10;
	mac_hook_t mpo_reserved11;
	mac_hook_t mpo_reserved12;
	mac_hook_t mpo_reserved13;
	mac_hook_t mpo_reserved14;
	mac_hook_t mpo_reserved15;
	mac_hook_t mpo_reserved16;
	mac_hook_t mpo_reserved17;
	mac_hook_t mpo_reserved18;
	mac_hook_t mpo_reserved19;
	mac_hook_t mpo_reserved20;
	mac_hook_t mpo_reserved21;
	mac_hook_t mpo_reserved22;

	mac_hook_t mpo_necp_check_open;
	mac_hook_t mpo_necp_check_client_action;

	mac_hook_t mpo_file_check_library_validation;

	mac_hook_t mpo_vnode_notify_setacl;
	mac_hook_t mpo_vnode_notify_setattrlist;
	mac_hook_t mpo_vnode_notify_setextattr;
	mac_hook_t mpo_vnode_notify_setflags;
	mac_hook_t mpo_vnode_notify_setmode;
	mac_hook_t mpo_vnode_notify_setowner;
	mac_hook_t mpo_vnode_notify_setutimes;
	mac_hook_t mpo_vnode_notify_truncate;
	mac_hook_t mpo_vnode_check_getattrlistbulk;

	mac_hook_t mpo_proc_check_get_task_special_port;
	mac_hook_t mpo_proc_check_set_task_special_port;

	mac_hook_t mpo_vnode_notify_swap;
	mac_hook_t mpo_vnode_notify_unlink;
	mac_hook_t mpo_vnode_check_swap;
	mac_hook_t mpo_vnode_check_dataprotect_set;
	mac_hook_t mpo_mount_check_remount_with_flags;
	mac_hook_t mpo_mount_notify_mount;
	mac_hook_t mpo_vnode_check_copyfile;

	mac_hook_t mpo_mount_check_quotactl;
	mac_hook_t mpo_mount_check_fsctl;
	mac_hook_t mpo_mount_check_getattr;
	mac_hook_t mpo_mount_check_label_update;
	mac_hook_t mpo_mount_check_mount;
	mac_hook_t mpo_mount_check_remount;
	mac_hook_t mpo_mount_check_setattr;
	mac_hook_t mpo_mount_check_stat;
	mac_hook_t mpo_mount_check_umount;
	mac_hook_t mpo_mount_label_associate;
	mac_hook_t mpo_mount_label_destroy;
	mac_hook_t mpo_mount_label_externalize;
	mac_hook_t mpo_mount_label_init;
	mac_hook_t mpo_mount_label_internalize;

	mac_hook_t *mpo_proc_check_expose_task_with_flavor;
	mac_hook_t mpo_proc_check_get_task_with_flavor;
	mac_hook_t mpo_proc_check_task_id_token_get_task;

	mac_hook_t mpo_pipe_check_ioctl;
	mac_hook_t mpo_pipe_check_kqfilter;
	mac_hook_t mpo_reserved41;
	mac_hook_t mpo_pipe_check_read;
	mac_hook_t mpo_pipe_check_select;
	mac_hook_t mpo_pipe_check_stat;
	mac_hook_t mpo_pipe_check_write;
	mac_hook_t mpo_pipe_label_associate;
	mac_hook_t mpo_reserved42;
	mac_hook_t mpo_pipe_label_destroy;
	mac_hook_t mpo_reserved43;
	mac_hook_t mpo_pipe_label_init;
	mac_hook_t mpo_reserved44;
	mac_hook_t mpo_proc_check_syscall_mac;

	mac_hook_t mpo_policy_destroy;
	mac_hook_t mpo_policy_init;
	mac_hook_t mpo_policy_initbsd;
	mac_hook_t mpo_policy_syscall;

	mac_hook_t mpo_system_check_sysctlbyname;
	mac_hook_t mpo_proc_check_inherit_ipc_ports;
	mac_hook_t mpo_vnode_check_rename;
	mac_hook_t mpo_kext_check_query;
	mac_hook_t mpo_proc_notify_exec_complete;
	mac_hook_t mpo_proc_notify_cs_invalidated;
	mac_hook_t mpo_proc_check_syscall_unix;
	mac_hook_t mpo_reserved45;
	mac_hook_t mpo_proc_check_set_host_special_port;
	mac_hook_t *mpo_proc_check_set_host_exception_port;
	mac_hook_t mpo_exc_action_check_exception_send;
	mac_hook_t mpo_exc_action_label_associate;
	mac_hook_t mpo_exc_action_label_populate;
	mac_hook_t mpo_exc_action_label_destroy;
	mac_hook_t mpo_exc_action_label_init;
	mac_hook_t mpo_exc_action_label_update;

	mac_hook_t mpo_vnode_check_trigger_resolve;
	mac_hook_t mpo_mount_check_mount_late;
	mac_hook_t mpo_mount_check_snapshot_mount;
	mac_hook_t mpo_vnode_notify_reclaim;
	mac_hook_t mpo_skywalk_flow_check_connect;
	mac_hook_t mpo_skywalk_flow_check_listen;

	mac_hook_t mpo_posixsem_check_create;
	mac_hook_t mpo_posixsem_check_open;
	mac_hook_t mpo_posixsem_check_post;
	mac_hook_t mpo_posixsem_check_unlink;
	mac_hook_t mpo_posixsem_check_wait;
	mac_hook_t mpo_posixsem_label_associate;
	mac_hook_t mpo_posixsem_label_destroy;
	mac_hook_t mpo_posixsem_label_init;
	mac_hook_t mpo_posixshm_check_create;
	mac_hook_t mpo_posixshm_check_mmap;
	mac_hook_t mpo_posixshm_check_open;
	mac_hook_t mpo_posixshm_check_stat;
	mac_hook_t mpo_posixshm_check_truncate;
	mac_hook_t mpo_posixshm_check_unlink;
	mac_hook_t mpo_posixshm_label_associate;
	mac_hook_t mpo_posixshm_label_destroy;
	mac_hook_t mpo_posixshm_label_init;

	mac_hook_t mpo_proc_check_debug;
	mac_hook_t mpo_proc_check_fork;
	mac_hook_t mpo_reserved61;
	mac_hook_t mpo_reserved62;
	mac_hook_t mpo_proc_check_getaudit;
	mac_hook_t mpo_proc_check_getauid;
	mac_hook_t mpo_reserved63;
	mac_hook_t mpo_proc_check_mprotect;
	mac_hook_t mpo_proc_check_sched;
	mac_hook_t mpo_proc_check_setaudit;
	mac_hook_t mpo_proc_check_setauid;
	mac_hook_t mpo_proc_check_iopolicysys;
	mac_hook_t mpo_proc_check_signal;
	mac_hook_t mpo_proc_check_wait;
	mac_hook_t mpo_proc_check_dump_core;
	mac_hook_t mpo_proc_check_remote_thread_create;

	mac_hook_t mpo_socket_check_accept;
	mac_hook_t mpo_socket_check_accepted;
	mac_hook_t mpo_socket_check_bind;
	mac_hook_t mpo_socket_check_connect;
	mac_hook_t mpo_socket_check_create;
	mac_hook_t mpo_reserved46;
	mac_hook_t mpo_reserved47;
	mac_hook_t mpo_reserved48;
	mac_hook_t mpo_socket_check_listen;
	mac_hook_t mpo_socket_check_receive;
	mac_hook_t mpo_socket_check_received;
	mac_hook_t mpo_reserved49;
	mac_hook_t mpo_socket_check_send;
	mac_hook_t mpo_socket_check_stat;
	mac_hook_t mpo_socket_check_setsockopt;
	mac_hook_t mpo_socket_check_getsockopt;

	mac_hook_t mpo_proc_check_get_movable_control_port;
	mac_hook_t mpo_proc_check_dyld_process_info_notify_register;
	mac_hook_t mpo_proc_check_setuid;
	mac_hook_t mpo_proc_check_seteuid;
	mac_hook_t mpo_proc_check_setreuid;
	mac_hook_t mpo_proc_check_setgid;
	mac_hook_t mpo_proc_check_setegid;
	mac_hook_t mpo_proc_check_setregid;
	mac_hook_t mpo_proc_check_settid;
	mac_hook_t mpo_proc_check_memorystatus_control;
	mac_hook_t mpo_reserved60;

	mac_hook_t mpo_thread_telemetry;

	mac_hook_t mpo_iokit_check_open_service;

	mac_hook_t mpo_system_check_acct;
	mac_hook_t mpo_system_check_audit;
	mac_hook_t mpo_system_check_auditctl;
	mac_hook_t mpo_system_check_auditon;
	mac_hook_t mpo_system_check_host_priv;
	mac_hook_t mpo_system_check_nfsd;
	mac_hook_t mpo_system_check_reboot;
	mac_hook_t mpo_system_check_settime;
	mac_hook_t mpo_system_check_swapoff;
	mac_hook_t mpo_system_check_swapon;
	mac_hook_t mpo_socket_check_ioctl;

	mac_hook_t mpo_sysvmsg_label_associate;
	mac_hook_t mpo_sysvmsg_label_destroy;
	mac_hook_t mpo_sysvmsg_label_init;
	mac_hook_t mpo_sysvmsg_label_recycle;
	mac_hook_t mpo_sysvmsq_check_enqueue;
	mac_hook_t mpo_sysvmsq_check_msgrcv;
	mac_hook_t mpo_sysvmsq_check_msgrmid;
	mac_hook_t mpo_sysvmsq_check_msqctl;
	mac_hook_t mpo_sysvmsq_check_msqget;
	mac_hook_t mpo_sysvmsq_check_msqrcv;
	mac_hook_t mpo_sysvmsq_check_msqsnd;
	mac_hook_t mpo_sysvmsq_label_associate;
	mac_hook_t mpo_sysvmsq_label_destroy;
	mac_hook_t mpo_sysvmsq_label_init;
	mac_hook_t mpo_sysvmsq_label_recycle;
	mac_hook_t mpo_sysvsem_check_semctl;
	mac_hook_t mpo_sysvsem_check_semget;
	mac_hook_t mpo_sysvsem_check_semop;
	mac_hook_t mpo_sysvsem_label_associate;
	mac_hook_t mpo_sysvsem_label_destroy;
	mac_hook_t mpo_sysvsem_label_init;
	mac_hook_t mpo_sysvsem_label_recycle;
	mac_hook_t mpo_sysvshm_check_shmat;
	mac_hook_t mpo_sysvshm_check_shmctl;
	mac_hook_t mpo_sysvshm_check_shmdt;
	mac_hook_t mpo_sysvshm_check_shmget;
	mac_hook_t mpo_sysvshm_label_associate;
	mac_hook_t mpo_sysvshm_label_destroy;
	mac_hook_t mpo_sysvshm_label_init;
	mac_hook_t mpo_sysvshm_label_recycle;

	mac_hook_t mpo_proc_notify_exit;
	mac_hook_t mpo_mount_check_snapshot_revert;
	mac_hook_t mpo_vnode_check_getattr;
	mac_hook_t mpo_mount_check_snapshot_create;
	mac_hook_t mpo_mount_check_snapshot_delete;
	mac_hook_t mpo_vnode_check_clone;
	mac_hook_t mpo_proc_check_get_cs_info;
	mac_hook_t mpo_proc_check_set_cs_info;

	mac_hook_t mpo_iokit_check_hid_control;

	mac_hook_t mpo_vnode_check_access;
	mac_hook_t mpo_vnode_check_chdir;
	mac_hook_t mpo_vnode_check_chroot;
	mac_hook_t mpo_vnode_check_create;
	mac_hook_t mpo_vnode_check_deleteextattr;
	mac_hook_t mpo_vnode_check_exchangedata;
	mac_hook_t mpo_vnode_check_exec;
	mac_hook_t mpo_vnode_check_getattrlist;
	mac_hook_t mpo_vnode_check_getextattr;
	mac_hook_t mpo_vnode_check_ioctl;
	mac_hook_t mpo_vnode_check_kqfilter;
	mac_hook_t mpo_vnode_check_label_update;
	mac_hook_t mpo_vnode_check_link;
	mac_hook_t mpo_vnode_check_listextattr;
	mac_hook_t mpo_vnode_check_lookup;
	mac_hook_t mpo_vnode_check_open;
	mac_hook_t mpo_vnode_check_read;
	mac_hook_t mpo_vnode_check_readdir;
	mac_hook_t mpo_vnode_check_readlink;
	mac_hook_t mpo_vnode_check_rename_from;
	mac_hook_t mpo_vnode_check_rename_to;
	mac_hook_t mpo_vnode_check_revoke;
	mac_hook_t mpo_vnode_check_select;
	mac_hook_t mpo_vnode_check_setattrlist;
	mac_hook_t mpo_vnode_check_setextattr;
	mac_hook_t mpo_vnode_check_setflags;
	mac_hook_t mpo_vnode_check_setmode;
	mac_hook_t mpo_vnode_check_setowner;
	mac_hook_t mpo_vnode_check_setutimes;
	mac_hook_t mpo_vnode_check_stat;
	mac_hook_t mpo_vnode_check_truncate;
	mac_hook_t mpo_vnode_check_unlink;
	mac_hook_t mpo_vnode_check_write;
	mac_hook_t mpo_vnode_label_associate_devfs;
	mac_hook_t mpo_vnode_label_associate_extattr;
	mac_hook_t mpo_vnode_label_associate_file;
	mac_hook_t mpo_vnode_label_associate_pipe;
	mac_hook_t mpo_vnode_label_associate_posixsem;
	mac_hook_t mpo_vnode_label_associate_posixshm;
	mac_hook_t mpo_vnode_label_associate_singlelabel;
	mac_hook_t mpo_vnode_label_associate_socket;
	mac_hook_t mpo_vnode_label_copy;
	mac_hook_t mpo_vnode_label_destroy;
	mac_hook_t mpo_vnode_label_externalize_audit;
	mac_hook_t mpo_vnode_label_externalize;
	mac_hook_t mpo_vnode_label_init;
	mac_hook_t mpo_vnode_label_internalize;
	mac_hook_t mpo_vnode_label_recycle;
	mac_hook_t mpo_vnode_label_store;
	mac_hook_t mpo_vnode_label_update_extattr;
	mac_hook_t mpo_vnode_label_update;
	mac_hook_t mpo_vnode_notify_create;
	mac_hook_t mpo_vnode_check_signature;
	mac_hook_t mpo_vnode_check_uipc_bind;
	mac_hook_t mpo_vnode_check_uipc_connect;

	mac_hook_t mpo_proc_check_run_cs_invalid;
	mac_hook_t mpo_proc_check_suspend_resume;

	mac_hook_t mpo_thread_userret;

	mac_hook_t mpo_iokit_check_set_properties;

	mac_hook_t *mpo_vnode_check_supplemental_signature;

	mac_hook_t mpo_vnode_check_searchfs;

	mac_hook_t mpo_priv_check;
	mac_hook_t mpo_priv_grant;

	mac_hook_t mpo_proc_check_map_anon;

	mac_hook_t mpo_vnode_check_fsgetpath;

	mac_hook_t mpo_iokit_check_open;

	mac_hook_t mpo_proc_check_ledger;

	mac_hook_t mpo_vnode_notify_rename;

	mac_hook_t mpo_vnode_check_setacl;

	mac_hook_t mpo_vnode_notify_deleteextattr;

	mac_hook_t mpo_system_check_kas_info;

	mac_hook_t mpo_vnode_check_lookup_preflight;

	mac_hook_t mpo_vnode_notify_open;

	mac_hook_t mpo_system_check_info;

	mac_hook_t mpo_pty_notify_grant;
	mac_hook_t mpo_pty_notify_close;

	mac_hook_t mpo_vnode_find_sigs;

	mac_hook_t mpo_kext_check_load;
	mac_hook_t mpo_kext_check_unload;

	mac_hook_t mpo_proc_check_proc_info;
	mac_hook_t mpo_vnode_notify_link;
	mac_hook_t mpo_iokit_check_filter_properties;
	mac_hook_t mpo_iokit_check_get_property;
};

Recovering the hooks

Now that we’ve defined the types inside of our disassembler, we can associate the hooks with their callbacks. Here, for example, the function pointer stored in mpo_vnode_check_open points to an unnamed function, allowing us to identify it as _hook_vnode_check_open.

mac_policy_ops

Fastening your SeatBelt

In the first part, we briefly saw that XNU provides hooking capabilities thourgh the MACF framework. In this section, we will take a greater look at this mechanism.

Let’s look at what happens when you perform an open() call. In our case, our dummy app benefits from the same default sandbox profile as other third party apps, but how is it defined ?s

When our app does an open(), the unix_syscall() dispatcher will take the value of the syscall and find the correct kernel function to call by looking at the sysent table. The execution flow eventually reaches vn_open_auth():

vn_open_auth

vn_authorize_open_existing() then calls mac_vnode_check_open(), which dispatches the vnode_check_open operation through the MAC Framework.

mac_vnode_check_open’s role is to enforce the policy checks, for this, it uses a MAC_CHECK macro which will handle the whole logic.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
// xnu/mac_vfs.c
int mac_vnode_check_open(vfs_context_t ctx, struct vnode *vp, int acc_mode)
{
	kauth_cred_t cred;
	int error;

#if SECURITY_MAC_CHECK_ENFORCE
	/* 21167099 - only check if we allow write */
	if (!mac_vnode_enforce) {
		return 0;
	}
#endif
	cred = vfs_context_ucred(ctx);
	if (!mac_cred_check_enforce(cred)) {
		return 0;
	}
	VFS_KERNEL_DEBUG_START1(52, vp);
	MAC_CHECK(vnode_check_open, cred, vp, mac_vnode_label(vp), acc_mode);
	VFS_KERNEL_DEBUG_END1(52, vp);
	return error;
}

MAC_CHECK performs the check by walking through the policy module list, goes through the operations list (mac_policy_ops) and searches for the corresponding operation slot name mpo_ + vnode_check_open pointing to the function to execute (which in this case is _hook_vnode_check_open()) thanks to the MAC_POLICY_ITERATE macro.

 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
// xnu/security/mac_internal.h
struct mac_policy_list_element {
	struct mac_policy_conf *mpc;
};

...

#define MAC_CHECK(check, args...) do {                            
    error = 0;                                                    
    MAC_POLICY_ITERATE({                                          
	    if (mpc->mpc_ops->mpo_ ## check != NULL) {                  
	            MAC_CHECK_CALL(check, mpc);                         
	            int __step_err = mpc->mpc_ops->mpo_ ## check (args);
	            MAC_CHECK_RSLT(check, mpc);                         
	            error = mac_error_select(__step_err, error);        
	    }                                                           
    });
} while (0)
#define MAC_POLICY_ITERATE(...) do {                         
    struct mac_policy_conf *mpc;                             
    u_int i;                                                 
                                                             
    for (i = 0; i < mac_policy_list.staticmax; i++) {        
	    mpc = mac_policy_list.entries[i].mpc;                  
	    if (mpc == NULL)                                       
	            continue;                                      
                                                             
	    __VA_ARGS__                                            
    }                                                        
    if (mac_policy_list_conditional_busy() != 0) {           
	    for (; i <= mac_policy_list.maxindex; i++) {           
	            mpc = mac_policy_list.entries[i].mpc;          
	            if (mpc == NULL)                               
	                    continue;                              
                                                             
	            __VA_ARGS__                                    
	    }                                                      
	    mac_policy_list_unbusy();                              
    }                                                        
} while (0)

Looking at mac_vnode_check_open’s decompiled code, you can recognize the macro being dispatched with the offset 0x858 corresponding to the offset to mpo_vnode_check_open inside the mac_policy_ops structure:

Here is a screenshot of the structure showing the offset: mpo_vnode_check_open offset

And here is the decompiled code showing the macro being called: mac_vnode_check_open

_hook_vnode_check_open() is then eventually executed, calling _sb_evaluate_internal() to evaluate if the operation is to be allowed, taking into account the process’s assigned sandbox profile.

Here is a visual representation showcasing what happens from the open() to the security evaluation by SeatBelt:


syscall to seatbelt syscall to seatbelt

Conclusion

In this first part, we looked at how SeatBelt integrates with XNU’s Mandatory Access Control Framework and how a filesystem operation eventually reaches the security policies. We now know how the kernel implements the sandbox. What remains is to understand what is evaluates; the sandbox profiles themselves. A later blogpost will focus on understanding the binary format and how SeatBelt interprets them.

References: