macOS MACF

Support HackTricks

Basic Information

MACF означає Mandatory Access Control Framework, що є системою безпеки, вбудованою в операційну систему для захисту вашого комп'ютера. Вона працює, встановлюючи суворі правила щодо того, хто або що може отримати доступ до певних частин системи, таких як файли, програми та системні ресурси. Застосовуючи ці правила автоматично, MACF забезпечує, що лише авторизовані користувачі та процеси можуть виконувати певні дії, зменшуючи ризик несанкціонованого доступу або шкідливих дій.

Зверніть увагу, що MACF насправді не приймає жодних рішень, оскільки просто перехоплює дії, залишаючи рішення модулям політики (розширенням ядра), які вона викликає, таким як AppleMobileFileIntegrity.kext, Quarantine.kext, Sandbox.kext, TMSafetyNet.kext та mcxalr.kext.

Flow

  1. Процес виконує syscall/mach trap

  2. Відповідна функція викликається всередині ядра

  3. Функція викликає MACF

  4. MACF перевіряє модулі політики, які запросили підключити цю функцію у своїй політиці

  5. MACF викликає відповідні політики

  6. Політики вказують, чи дозволяють або забороняють дію

Apple є єдиною компанією, яка може використовувати KPI MAC Framework.

Labels

MACF використовує мітки, які потім політики перевіряють, чи повинні вони надати доступ чи ні. Код оголошення структури міток можна знайти тут, який потім використовується всередині struct ucred тут в частині cr_label. Мітка містить прапорці та кількість слотів, які можуть використовуватися політиками MACF для виділення вказівників. Наприклад, Sanbox вказуватиме на профіль контейнера.

MACF Policies

Політика MACF визначає правила та умови, які застосовуються до певних операцій ядра.

Розширення ядра може налаштувати структуру mac_policy_conf, а потім зареєструвати її, викликавши mac_policy_register. З тут:

#define mpc_t	struct mac_policy_conf *

/**
@brief Mac policy configuration

This structure specifies the configuration information for a
MAC policy module.  A policy module developer must supply
a short unique policy name, a more descriptive full name, a list of label
namespaces and count, a pointer to the registered enty point operations,
any load time flags, and optionally, a pointer to a label slot identifier.

The Framework will update the runtime flags (mpc_runtime_flags) to
indicate that the module has been registered.

If the label slot identifier (mpc_field_off) is NULL, the Framework
will not provide label storage for the policy.  Otherwise, the
Framework will store the label location (slot) in this field.

The mpc_list field is used by the Framework and should not be
modified by policies.
*/
/* XXX - reorder these for better aligment on 64bit platforms */
struct mac_policy_conf {
const char		*mpc_name;		/** policy name */
const char		*mpc_fullname;		/** full name */
const char		**mpc_labelnames;	/** managed label namespaces */
unsigned int		 mpc_labelname_count;	/** number of managed label namespaces */
struct mac_policy_ops	*mpc_ops;		/** operation vector */
int			 mpc_loadtime_flags;	/** load time flags */
int			*mpc_field_off;		/** label slot */
int			 mpc_runtime_flags;	/** run time flags */
mpc_t			 mpc_list;		/** List reference */
void			*mpc_data;		/** module data */
};

Легко ідентифікувати розширення ядра, які налаштовують ці політики, перевіряючи виклики до mac_policy_register. Більше того, перевіряючи дизасемблювання розширення, також можна знайти використану структуру mac_policy_conf.

Зверніть увагу, що політики MACF можуть бути зареєстровані та скасовані також динамічно.

Одним з основних полів mac_policy_conf є mpc_ops. Це поле вказує, які операції цікавлять політику. Зверніть увагу, що їх сотні, тому можливо обнулити всі з них, а потім вибрати лише ті, які цікавлять політику. З тут:

struct mac_policy_ops {
mpo_audit_check_postselect_t		*mpo_audit_check_postselect;
mpo_audit_check_preselect_t		*mpo_audit_check_preselect;
mpo_bpfdesc_label_associate_t		*mpo_bpfdesc_label_associate;
mpo_bpfdesc_label_destroy_t		*mpo_bpfdesc_label_destroy;
mpo_bpfdesc_label_init_t		*mpo_bpfdesc_label_init;
mpo_bpfdesc_check_receive_t		*mpo_bpfdesc_check_receive;
mpo_cred_check_label_update_execve_t	*mpo_cred_check_label_update_execve;
mpo_cred_check_label_update_t		*mpo_cred_check_label_update;
[...]

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:

int
mmap(proc_t p, struct mmap_args *uap, user_addr_t *retval)
{
[...]
#if CONFIG_MACF
			error = mac_file_check_mmap(vfs_context_ucred(ctx),
			    fp->fp_glob, prot, flags, file_pos + pageoff,
&maxprot);
if (error) {
(void)vnode_put(vp);
goto bad;
}
#endif /* MAC */
[...]

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

mac_file_check_mmap(struct ucred *cred, struct fileglob *fg, int prot,
int flags, uint64_t offset, int *maxprot)
{
int error;
int maxp;

maxp = *maxprot;
MAC_CHECK(file_check_mmap, cred, fg, NULL, prot, flags, offset, &maxp);
if ((maxp | *maxprot) != *maxprot) {
panic("file_check_mmap increased max protections");
}
*maxprot = maxp;
return error;
}

Який викликає макрос MAC_CHECK, код якого можна знайти за посиланням https://github.com/apple-oss-distributions/xnu/blob/94d3b452840153a99b38a3a9659680b2a006908e/security/mac_internal.h#L261

/*
* MAC_CHECK performs the designated check by walking the policy
* module list and checking with each as to how it feels about the
* request.  Note that it returns its value via 'error' in the scope
* of the caller.
*/
#define MAC_CHECK(check, args...) do {                              \
error = 0;                                                      \
MAC_POLICY_ITERATE({                                            \
if (mpc->mpc_ops->mpo_ ## check != NULL) {              \
DTRACE_MACF3(mac__call__ ## check, void *, mpc, int, error, int, MAC_ITERATE_CHECK); \
int __step_err = mpc->mpc_ops->mpo_ ## check (args); \
DTRACE_MACF2(mac__rslt__ ## check, void *, mpc, int, __step_err); \
error = mac_error_select(__step_err, error);         \
}                                                           \
});                                                             \
} while (0)

Який пройде через всі зареєстровані політики mac, викликаючи їх функції та зберігаючи вихідні дані в змінній помилки, яка може бути перевизначена лише mac_error_select кодами успіху, тому якщо будь-яка перевірка не пройде, вся перевірка не пройде, і дія не буде дозволена.

Однак пам'ятайте, що не всі виклики MACF використовуються лише для відмови в діях. Наприклад, mac_priv_grant викликає макрос MAC_GRANT, який надасть запитувану привілегію, якщо будь-яка політика відповість 0:

/*
* MAC_GRANT performs the designated check by walking the policy
* module list and checking with each as to how it feels about the
* request.  Unlike MAC_CHECK, it grants if any policies return '0',
* and otherwise returns EPERM.  Note that it returns its value via
* 'error' in the scope of the caller.
*/
#define MAC_GRANT(check, args...) do {                              \
error = EPERM;                                                  \
MAC_POLICY_ITERATE({                                            \
if (mpc->mpc_ops->mpo_ ## check != NULL) {                  \
DTRACE_MACF3(mac__call__ ## check, void *, mpc, int, error, int, MAC_ITERATE_GRANT); \
int __step_res = mpc->mpc_ops->mpo_ ## check (args); \
if (__step_res == 0) {                              \
error = 0;                                  \
}                                                   \
DTRACE_MACF2(mac__rslt__ ## check, void *, mpc, int, __step_res); \
}                                                           \
});                                                             \
} while (0)

priv_check & priv_grant

Ці виклики призначені для перевірки та надання (десятків) привілеїв, визначених у bsd/sys/priv.h. Деякий код ядра викликатиме priv_check_cred() з bsd/kern/kern_priv.c з KAuth обліковими даними процесу та одним з кодів привілеїв, який викликатиме mac_priv_check, щоб перевірити, чи будь-яка політика забороняє надання привілею, а потім викликатиме mac_priv_grant, щоб перевірити, чи будь-яка політика надає привілей.

proc_check_syscall_unix

Цей хук дозволяє перехоплювати всі системні виклики. У bsd/dev/[i386|arm]/systemcalls.c можна побачити оголошену функцію unix_syscall, яка містить цей код:

#if CONFIG_MACF
if (__improbable(proc_syscall_filter_mask(proc) != NULL && !bitstr_test(proc_syscall_filter_mask(proc), syscode))) {
error = mac_proc_check_syscall_unix(proc, syscode);
if (error) {
goto skip_syscall;
}
}
#endif /* CONFIG_MACF */

Який перевірить у викликаючому процесі бітову маску, чи слід поточному системному виклику викликати mac_proc_check_syscall_unix. Це пов'язано з тим, що системні виклики викликаються так часто, що цікаво уникати виклику mac_proc_check_syscall_unix щоразу.

Зверніть увагу, що функція proc_set_syscall_filter_mask(), яка встановлює бітову маску системних викликів у процесі, викликається Sandbox для встановлення масок на пісочницях.

Відкриті системні виклики MACF

Можливо взаємодіяти з MACF через деякі системні виклики, визначені в security/mac.h:

/*
* Extended non-POSIX.1e interfaces that offer additional services
* available from the userland and kernel MAC frameworks.
*/
#ifdef __APPLE_API_PRIVATE
__BEGIN_DECLS
int      __mac_execve(char *fname, char **argv, char **envv, mac_t _label);
int      __mac_get_fd(int _fd, mac_t _label);
int      __mac_get_file(const char *_path, mac_t _label);
int      __mac_get_link(const char *_path, mac_t _label);
int      __mac_get_pid(pid_t _pid, mac_t _label);
int      __mac_get_proc(mac_t _label);
int      __mac_set_fd(int _fildes, const mac_t _label);
int      __mac_set_file(const char *_path, mac_t _label);
int      __mac_set_link(const char *_path, mac_t _label);
int      __mac_mount(const char *type, const char *path, int flags, void *data,
struct mac *label);
int      __mac_get_mount(const char *path, struct mac *label);
int      __mac_set_proc(const mac_t _label);
int      __mac_syscall(const char *_policyname, int _call, void *_arg);
__END_DECLS
#endif /*__APPLE_API_PRIVATE*/

References

Support HackTricks

Last updated