XNU kauth 子系統解讀

作者:wzt
原文鏈接:https://mp.weixin.qq.com/s/Tm4z-_po6DmurcneKQ536A

1.1 簡介

XNU將進程憑證credential、文件系統acl授權、進程和文件系統監控這幾個安全功能抽象爲一個安全框架,叫做kauth子系統。它的具體功能主要包含:

- 進程憑證credential的創建、更新、銷燬。

- 文件系統acl的創建、評估、銷燬。

- 提供kauth scope框架,對進程、文件系統做監控, 支持系統默認監控函數,提供kpi接口,使得第三方內核擴展可以動態添加鉤子。

1.2 進程憑證維護

1.2.1 基本結構

XNU與傳統UNIX的進程憑證cred稍有不同。

bsd/sys/ucred.h:
struct ucred {
        TAILQ_ENTRY(ucred)      cr_link; /* never modify this without KAUTH_CRED_HASH_LOCK */
        u_long  cr_ref;                 /* reference count */
struct posix_cred {
        uid_t   cr_uid;                 /* effective user id */
        uid_t   cr_ruid;                /* real user id */
        uid_t   cr_svuid;               /* saved user id */
        short   cr_ngroups;             /* number of groups in advisory list */
        gid_t   cr_groups[NGROUPS];     /* advisory group list */
        gid_t   cr_rgid;                /* real group id */
        gid_t   cr_svgid;               /* saved group id */
        uid_t   cr_gmuid;               /* UID for group membership purposes */
        int     cr_flags;               /* flags on credential */
} cr_posix;
        struct label    *cr_label;      /* MAC label */
        struct au_session cr_audit;             /* user auditing data */
};

struct ucred基本繼承了BSD的ucred結構,保留了MAC label和audit審計成員,因爲xnu內核完全繼承了BSD的MAC和audit子系統能力。同時去掉了poison成員, xnu沒有使用bsd jail的功能,對於xnu的沙箱功能,在後面的系列文章中在詳細介紹。xnu ucred仍然包含用戶所在的組概念,成員cr_groups數組長度爲16,是unix家族中標準的用戶組大小。除了cr_groups,xnu使用kauth子系統擴展了用戶所在組的概念,cr_groups包含的僅是本地機器的用戶組,Mac OS可以作爲服務器使用,kauth建立了一種額外的擴展能力,可以將網絡上的其他機器用戶組包含到本地組裏。

XNU是一個混合的內核,mach微內核部分控制內核的進程創建與調度功能, mach的進程結構體包含了指向bsd進程和線程結構體的指針,而cred是BSD內核的功能,自然包含在bsd封裝的進程和線程結構體裏,它們之間的數據結構體關係如下:

內核通過current_thread()宏來獲取mach層的struct thread指針,以i386架構爲例:

osfmk/i386/cpu_data.h

#define current_thread_fast()           get_active_thread()
#define current_thread()                current_thread_fast()

static inline __pure2 thread_t
get_active_thread(void)
{
        CPU_DATA_GET(cpu_active_thread,thread_t)
}

#define CPU_DATA_GET(member,type)                                       \
        type ret;                                                       \
        __asm__ volatile ("mov %%gs:%P1,%0"                             \
                : "=r" (ret)                                            \
                : "i" (offsetof(cpu_data_t,member)));                   \
        return ret;

typedef struct cpu_data
{
        thread_t                cpu_active_thread;
} cpu_data_t;

cpu_data_t結構體裏的cpu_active_thread成員指向的就是當前cpu指向的mac thread指針, 通過offsetof宏計算處它的偏移,在i386下%gs:offset保存的就是它的地址。X64下保存在%fs:offset。

內核通過get_bsdthread_info函數獲取mac thread指向的bsd thread指針:

osfmk/kern/bsd_kern.c

void *get_bsdthread_info(thread_t th)
{
        return(th->uthread);
}

內核通過get_threadtask函數獲取mac thread指向的mac task指針,然後就可以通過mac task找到bsd進程的proc指針。

osfmk/kern/bsd_kern.c

task_t  get_threadtask(thread_t th)
{
        return(th->task);
}

void  *get_bsdtask_info(task_t t)
{
        return(t->bsd_info);
}

內核通過kauth_cred_get函數獲取進程的cred結構指針,根據以上結構信息也就不難理解了。

bsd/kern/kern_credential.c

kauth_cred_t
kauth_cred_get(void)
{
        struct proc *p;
        struct uthread *uthread;

        uthread = get_bsdthread_info(current_thread());
        if (uthread == NULL)
                panic("thread wants credential but has no BSD thread info");

        if (uthread->uu_ucred == NOCRED) {
                if ((p = (proc_t) get_bsdtask_info(get_threadtask(current_thread()))) == NULL)
                        panic("thread wants credential but has no BSD process");
                uthread->uu_ucred = kauth_cred_proc_ref(p);
        }
        return(uthread->uu_ucred);
}

1.2.2 cred維護

XNU對於進程cred的維護與BSD、linux有所不同, 它將每個進程的cred緩存在一個hash表裏,對於複製cred等操作,可以通過引用計數來實現,在它的代碼註釋中提到這種優化對於一個桌面系統來講,可以至少節省200k左右的內存。

1.2.3 組擴展機制

前面提到xnu擴展了bsd的用戶組管理機制,在內核中叫做kauth resolver機制, 在kauth初始化時,建立了幾個隊列,當內核使用cred進行用戶組授權的過程中, 將本次請求封裝爲一個worker,加入相應的隊列中等待用戶態進程進行處理。xnu增加了一個系統調用identitysvc,用戶進程使用這個系統調用與kauth通訊,比如獲取等待隊列中的worker,然後在用戶態進行處理。用戶組涉及到的處理邏輯相對複雜,xnu直接引用了windows nt內核的sid概念來完善kuath授權系統。

1.2.3.1 kauth resolver機制初始化

bsd/kern/kern_authorization.c

void
kauth_init(void)
{
#if CONFIG_EXT_RESOLVER
        kauth_identity_init();[1]
        kauth_groups_init();[2]
#endif

#if CONFIG_EXT_RESOLVER
        kauth_resolver_init();[3]
#endif
}

Kauth_init在初始時[1]處調用kauth_identity_init()初始化kauth_identities鏈表,每個節點是struct kauth_identity結構體,這個鏈表用來緩存cred的身份信息,因爲如果每次身份驗證時都要用戶態進程參與,那麼效率將會非常低,kauth在每次用戶態驗證完時,將驗證成功的身份信息緩存在kauth_identities鏈表裏,下次驗證時將在緩存裏進行搜索,如果沒有匹配到,在通知用戶態進程處理。[2]處的kauth_groups_init()函數功能機理與上述一致。[3]處的kauth_resolver_init函數初始化了三個隊列,分別爲kauth_resolver_unsubmitted、kauth_resolver_submitted、kauth_resolver_done。

1.2.3.2 identitysvc系統調用

用戶態進程通過調用identitysvc系統調用在內核中進行註冊,更新緩存大小、獲取處理任務以及發送任務的處理結果。

先來看下用戶進程的註冊過程。

bsd/kern/kern_credential.c

int
identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused int32_t *retval)
{
 if (opcode == KAUTH_EXTLOOKUP_REGISTER) {
                new_id = current_proc()->p_pid;
                if ((error = kauth_authorize_generic(kauth_cred_get(), KAUTH_GENERIC_ISSUSER)) != 0) {                                            [1]
                        KAUTH_DEBUG("RESOLVER - pid %d refused permission to become identity resolver", new_id);
                        return(error);
                }

                if (kauth_resolver_identity != new_id) {[2]
                        kauth_resolver_identity = new_id;[3]
                        kauth_resolver_registered = 1;
                        wakeup(&kauth_resolver_unsubmitted);[4]
}

當來自用戶空間的請求碼爲KAUTH_EXTLOOKUP_REGISTER時, 在[1]處調用 kauth_authorize_generic, 這是後面將要講到的kauth scope監控機制,當前內核的默認授權只是檢測當前進程uid是不是爲0, 也就是說只有root權限用戶纔可以註冊。[2]處判斷當前進程和之前註冊的進程號是否相同,如果不相同就會用當前進程號替換原來的進程號kauth_resolver_identity,然後在[4]處喚醒kauth_resolver_unsubmitted等待隊列上的進程。

我們看到用戶進程的註冊過程相當簡單,這裏就會有幾個安全問題。所有root進程都可以進行註冊,linux使用了capability進一步將root權限進行了劃分, 比如auditd的註冊就需要有CAP_NET_ADMIN這個能力纔可以。而XNU並沒有繼承BSD的capability能力模型以及privilege特權模型,這使得它對內核權限的控制就沒有那麼細緻化。其次新的用戶進程直接就可以替換老的用戶進程,並沒有使用一些可信驗證手段, 這使得任何的惡意root進程都可以對其進行替換和仿冒,這樣身份驗證機制就形同虛設了。

我們在來看下用戶進程是如何從內核獲取任務的。

bsd/kern/kern_credential.c

int
identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused int32_t *retval)
{
        if (opcode & KAUTH_EXTLOOKUP_WORKER) {
                if ((error = kauth_resolver_getwork(message)) != 0)
                        return(error);
        }
}

static int
kauth_resolver_getwork(user_addr_t message)
{
        struct kauth_resolver_work *workp;

        while ((workp = TAILQ_FIRST(&kauth_resolver_unsubmitted)) == NULL) {
                thread_t thread = current_thread();
                struct uthread *ut = get_bsdthread_info(thread);

                ut->uu_save.uus_kauth.message = message;
                error = msleep0(&kauth_resolver_unsubmitted, kauth_resolver_mtx, PCATCH, "GRGetWork", 0, kauth_resolver_getwork_continue);
                KAUTH_RESOLVER_UNLOCK();

                if (!kauth_resolver_identity) {
                        printf("external resolver died");
                        error = KAUTH_RESOLVER_FAILED_ERRCODE;
                }

                return(error);
        }

        return kauth_resolver_getwork2(message);
}

kauth_resolver_getwork函數用戶獲取內核任務, 首先判斷kauth_resolver_unsubmitted隊列是否爲空,這個隊列保存的是內核發佈的等待用戶進程獲取的任務節點,下一小節會對其進行描述。如果隊列爲空,就使用msleep進行睡眠,同時回調函數設置爲kauth_resolver_getwork_continue,這個函數只是繼續判斷隊列是否爲空,然後遞歸調用自己。當隊列不爲空時,會調用kauth_resolver_getwork2。它從kauth_resolver_unsubmitted隊列頭取下一個節點,用copyout函數拷貝給用戶空間的進程,然後將這個節點移入到kauth_resolver_submitted隊列,這樣用戶進程就獲取了要進行身份驗證的信息。

當用戶進程處理完畢後,處理結果要返回給內核。

bsd/kern/kern_credential.c

int
identitysvc(__unused struct proc *p, struct identitysvc_args *uap, __unused int32_t *retval)
{
        if (opcode & KAUTH_EXTLOOKUP_RESULT) {
                if ((error = kauth_resolver_complete(message)) != 0)
                        return(error);
        }
}

static int
kauth_resolver_complete(user_addr_t message)
{
        if ((error = copyin(message, &extl, sizeof(extl))) != 0) {
                KAUTH_DEBUG("RESOLVER - error getting completed work\n");
                return(error);
        }

        if (extl.el_result != KAUTH_EXTLOOKUP_FATAL) {
                TAILQ_FOREACH(workp, &kauth_resolver_submitted, kr_link) {
                        if (workp->kr_seqno == extl.el_seqno) {
                                TAILQ_INSERT_TAIL(&kauth_resolver_done, workp, kr_link);
}

kauth_resolver_complete通過copyin將用戶信息拷貝到內核,然後遍歷kauth_resolver_submitted隊列,根據seq號找到對應的節點,更新處理信息,然後將這個節點移動到kauth_resolver_done隊列。

1.2.3.3 cred身份驗證

當涉及到cred的身份驗證時,kauth調用kauth_cred_cache_lookup函數進行處理。

bsd/kern/kern_credential.c

static int
kauth_cred_cache_lookup(int from, int to, void *src, void *dst)
{
        switch(from) {
        case KI_VALID_UID:[1]
                error = kauth_identity_find_uid(*(uid_t *)src, &ki, namebuf);
                if (expired) {
                        if (!expired(&ki)) {[2]
                                KAUTH_DEBUG("CACHE - entry valid, unexpired");

        error = kauth_resolver_submit(&el, extend_data);[3]
        if (error == 0) {
                kauth_identity_updatecache(&el, &ki, extend_data);[4]
}

kauth在kauth_identities cache中維護着一個轉換列表,cred中的uid可以對應kauth_identities中的guid、ntsid等等。比如轉換類型爲KI_VALID_UID,則在[1]處調用kauth_identity_find_uid,在cache中進行搜索。找到後,還要在[2]處進行驗證身份信息是否過

static int

kauth_resolver_submit(struct kauth_identity_extlookup *lkp, uint64_t extend_data)
{
        struct kauth_resolver_work *workp, *killp;

        MALLOC(workp, struct kauth_resolver_work *, sizeof(*workp), M_KAUTH, M_WAITOK); [1]
        if (workp == NULL)
                return(ENOMEM);

        workp->kr_work = *lkp;
        workp->kr_extend = extend_data;
        workp->kr_refs = 1;
        workp->kr_flags = KAUTH_REQUEST_UNSUBMITTED;
        workp->kr_result = 0;

        KAUTH_RESOLVER_LOCK();
        workp->kr_seqno = workp->kr_work.el_seqno = kauth_resolver_sequence++;
        workp->kr_work.el_result = KAUTH_EXTLOOKUP_INPROG;

TAILQ_INSERT_TAIL(&kauth_resolver_unsubmitted, workp, kr_link);    [2]
    wakeup_one((caddr_t)&kauth_resolver_unsubmitted);   [3]
    error = __KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER__(workp); [4]
    if (error == 0)
    *lkp = workp->kr_work;   [5]

}

期。如果沒找到會在[3]處調用kauth_resolver_submit, 將當前處理信息封裝爲一個struct kauth_identity_extlookup結構體發送到等待隊列中進行處理。

[1] 處封裝爲一個struct kauth_resolver_work worker節點,在[2]處掛接到kauth_resolver_unsubmitted隊列末尾,在[3]處喚醒在這個等待隊列上睡眠的進程,通常爲用戶態的memberd守護進程。然後在[4]處調用 _KERNEL_IS_WAITING_ON_EXTERNAL_CREDENTIAL_RESOLVER_函數,它一直調用msleep睡眠kauth_resolver_timeout秒,再次被喚醒後,檢查worker的狀態是否爲KAUTH_REQUEST_DONE,如果是則函數返回,否則繼續睡眠重複上述行爲。當worker被處理完畢後,在[5]處保存更新後的信息。這個信息是用戶態進程處理完畢後使用identitysvc系統調用進行同步的。回到kauth_cred_cache_lookup函數,它將調用kauth_identity_updatecache在緩存中更新相關信息。

1.2.4 進程和文件系統監控

1.2.4.1 kauth scope框架

Kauth定義了一個scope監控框架,提供默認和第三方內核擴展回調函數支持,可以對進程和文件系統的關鍵行爲進行監控。

監控類型有幾下幾種:

bsd/sys/kauth.h

#define KAUTH_SCOPE_GENERIC     "com.apple.kauth.generic"
#define KAUTH_SCOPE_PROCESS     "com.apple.kauth.process"
#define KAUTH_SCOPE_VNODE       "com.apple.kauth.vnode"
#define KAUTH_SCOPE_FILEOP      "com.apple.kauth.fileop"

KAUTH_SCOPE_GENERIC是通用的內核事件監控函數,比如在前面章節講到的用戶態進程註冊kauth resovler時就調用了它的默認監控函數,只判斷進程的uid號是否爲0。

KAUTH_SCOPE_PROCESS提供進程事件的相關監控,目前只對能否向目標進程發送信號和是否有調試權限做了監控。

KAUTH_SCOPE_VNODE提供了對vnode的權限檢查以及acl評估功能。

KAUTH_SCOPE_FILEOP提供了對文件狀態和屬性更改的監控,它類似於linux的fsnotify文件系統監控框架。

Kauth子系統定義了struct kauth_scope結構:

#define KAUTH_SCOPE_MAX_LISTENERS  15

struct kauth_scope {
        TAILQ_ENTRY(kauth_scope)        ks_link;
        volatile struct kauth_local_listener  ks_listeners[KAUTH_SCOPE_MAX_LISTENERS];
        const char *                            ks_identifier;
        kauth_scope_callback_t          ks_callback;
        void *                                          ks_idata;
        u_int                                           ks_flags;
};

ks_callback即爲默認的callback函數。ks_listeners爲第三方內核擴展定義的callback函數。每個scope最多有15個擴展回調函數。

struct kauth_local_listener {
        kauth_listener_t                        kll_listenerp;
        kauth_scope_callback_t          kll_callback;
        void *                                          kll_idata;
}

內核使用kauth_register_scope註冊一個scope。

kauth_scope_t

kauth_register_scope(const char *identifier, kauth_scope_callback_t callback, void *idata)
{
        kauth_scope_t           sp, tsp;
        kauth_listener_t        klp;

        if ((sp = kauth_alloc_scope(identifier, callback, idata)) == NULL)
                return(NULL);

        KAUTH_SCOPELOCK();
        TAILQ_FOREACH(tsp, &kauth_scopes, ks_link) {
                if (strncmp(tsp->ks_identifier, identifier,
                                        strlen(tsp->ks_identifier) + 1) == 0) {
                        KAUTH_SCOPEUNLOCK();
                        FREE(sp, M_KAUTH);
                        return(NULL);
                }
        }

        TAILQ_INSERT_TAIL(&kauth_scopes, sp, ks_link);
restart:
        TAILQ_FOREACH(klp, &kauth_dangling_listeners, kl_link) {
                if (strncmp(klp->kl_identifier, sp->ks_identifier,
                                        strlen(klp->kl_identifier) + 1) == 0) {
                        if (kauth_add_callback_to_scope(sp, klp) == 0) {
                                TAILQ_REMOVE(&kauth_dangling_listeners, klp, kl_link);
                        }

                        else {
                                break;
                        }

                        goto restart;
                }
        }

        KAUTH_SCOPEUNLOCK();
        return(sp);
}

所有scope存在於kauth_scopes鏈表,kauth_register_scope首先根據名稱搜索是否已經存在重名的scope節點,存在直接返回。如果不存在的話,將其掛接於kauth_scopes鏈表末尾。然後它遍歷kauth_dangling_listeners鏈表,這裏保存的是備用的第三方回調函數節點kauth listener, 調用kauth_add_callback_to_scope將其添加到對應的scope listener數組裏。

static int kauth_add_callback_to_scope(kauth_scope_t sp, kauth_listener_t klp)
{
        int             i;

        for (i = 0; i < KAUTH_SCOPE_MAX_LISTENERS; i++) {
                if (sp->ks_listeners[i].kll_listenerp == NULL) {
                        sp->ks_listeners[i].kll_callback = klp->kl_callback;
                        sp->ks_listeners[i].kll_idata = klp->kl_idata;
                        sp->ks_listeners[i].kll_listenerp = klp;
                        sp->ks_flags |= KS_F_HAS_LISTENERS;
                        return(0);
             }
        }

        return(ENOSPC);
}

內核使用kauth_listen_scope函數註冊一個第三方內核擴展listener到一個scope上。

kauth_listener_t

kauth_listen_scope(const char *identifier, kauth_scope_callback_t callback, void *idata)
{
        kauth_listener_t klp;
        kauth_scope_t   sp;

        if ((klp = kauth_alloc_listener(identifier, callback, idata)) == NULL)
                return(NULL);

        KAUTH_SCOPELOCK();
        TAILQ_FOREACH(sp, &kauth_scopes, ks_link) {
                if (strncmp(sp->ks_identifier, identifier,
                                        strlen(sp->ks_identifier) + 1) == 0) {

                        if (kauth_add_callback_to_scope(sp, klp) == 0) {
                                KAUTH_SCOPEUNLOCK();
                                return(klp);
                        }

                        KAUTH_SCOPEUNLOCK();
                        FREE(klp, M_KAUTH);
                        return(NULL);
                }
        } 

        TAILQ_INSERT_TAIL(&kauth_dangling_listeners, klp, kl_link);
        KAUTH_SCOPEUNLOCK();

        return(klp);
}

它的註冊邏輯也非常簡單,首先遍歷kauth_scopes鏈表找到對應的scope,如果找到,就調用kauth_add_callback_to_scope將其加入scope的listener數組裏。如果沒找到,將這個節點掛接於kauth_dangling_listeners備用鏈表中, 當需要的scope被註冊時,會自動從kauth_dangling_listeners鏈表中找到這個節點並掛接上去。

在需要監控的內核路徑中, 會調用kauth_authorize_action函數。

int

kauth_authorize_action(kauth_scope_t scope, kauth_cred_t credential, kauth_action_t action,
    uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3)
{
        int result, ret, i;

        if (scope->ks_callback != NULL)
                result = scope->ks_callback(credential, scope->ks_idata, action, arg0, arg1,
        else
                result = KAUTH_RESULT_DEFER;

        if ((scope->ks_flags & KS_F_HAS_LISTENERS) != 0) {
                for (i = 0; i < KAUTH_SCOPE_MAX_LISTENERS; i++) {
                        ret = scope->ks_listeners[i].kll_callback(
                                        credential, scope->ks_listeners[i].kll_idata,
                                        action, arg0, arg1, arg2, arg3);
                        if ((ret == KAUTH_RESULT_DENY) ||
                                (result == KAUTH_RESULT_DEFER))
                                result = ret;
                }
        }
        return(result == KAUTH_RESULT_ALLOW ? 0 : EPERM);
}

首先它會調用默認的回調函數,然後如果此scope有listener,則將依次調用listener註冊的回調函數, 算法有點類似acl評估機制,如果有一個listener拒絕的話就直接返回失敗。

1.2.4.2 進程監控

Kauth在初始化的時候調用kauth_scope_init初始化三個監控類型的scope。

static void
kauth_scope_init(void)
{
        kauth_scope_mtx = lck_mtx_alloc_init(kauth_lck_grp, 0 /*LCK_ATTR_NULL*/);
        kauth_scope_process = kauth_register_scope(KAUTH_SCOPE_PROCESS, kauth_authorize_process_callback, NULL);
        kauth_scope_generic = kauth_register_scope(KAUTH_SCOPE_GENERIC, kauth_authorize_generic_callback, NULL);
        kauth_scope_fileop = kauth_register_scope(KAUTH_SCOPE_FILEOP, NULL, NULL);
}

對於進程的監控,註冊的默認回調函數爲kauth_authorize_process_callback。

static int

kauth_authorize_process_callback(kauth_cred_t credential, __unused void *idata, kauth_action_t action,
    uintptr_t arg0, uintptr_t arg1, __unused uintptr_t arg2, __unused uintptr_t arg3)
{
        switch(action) {
        case KAUTH_PROCESS_CANSIGNAL:
                panic("KAUTH_PROCESS_CANSIGNAL not implemented");
                if (cansignal(current_proc(), credential, (struct proc *)arg0, (int)arg1))
                        return(KAUTH_RESULT_ALLOW);
                break;
        case KAUTH_PROCESS_CANTRACE:
                if (cantrace(current_proc(), credential, (proc_t)arg0, (int *)arg1))
                        return(KAUTH_RESULT_ALLOW);
                break;
        }

        return(KAUTH_RESULT_DEFER);
}

回調函數非常簡單,只判斷進程是否有trace能力,對於是否能有發送信號的能力,xnu內核開發者估計也沒想好,panic函數直接寫在了cansignal函數的前面。

1.2.4.3 文件狀態監控

在kauth_scope_init初始化時,並沒有對KAUTH_SCOPE_FILEOP類型的scope設置默認回調函數。

static void

kauth_scope_init(void)
{
        kauth_scope_fileop = kauth_register_scope(KAUTH_SCOPE_FILEOP, NULL, NULL);
}

在文件狀態發生變更的地方,都調用了kauth_authorize_fileop函數,它繼而調用kauth_authorize_action函數,由於KAUTH_SCOPE_FILEOP類型的scope沒有默認回調函數,它將繼續判斷是否有加載第三方內核擴展的回調函數。對於文件系統狀態監控,開發人員需要自己編寫一個內核擴展註冊listene回調函數到scope中才行,xnu內核並沒有提供默認的內核擴展。

kauth_authorize_fileop函數定義爲:

int

kauth_authorize_fileop(kauth_cred_t credential, kauth_action_t action, uintptr_t arg0, uintptr_t arg1);

內核在文件系統的不同路徑調用它,最後兩個參數在不同的調用路徑對應不同的意義。內核註釋代碼中寫的很詳細:

1.2.4.4 文件vnode授權與acl檢查

對於文件vnode監控的kauth scope註冊是放在文件系統初始化進行的:

bsd/vfs/vfs_subr.c

void
vnode_authorize_init(void)
{
        vnode_scope = kauth_register_scope(KAUTH_SCOPE_VNODE, vnode_authorize_callback, NULL);
}

它註冊的callback函數爲vnode_authorize_callback。vnode的權限檢查包括以下幾個:

爲了加快檢查速度,xnu使用了一個cache機制,在vnode的結構體加入了v_authorized_actions成員,它代表了上一次是做的哪項權限檢查,通過調用vnode_cache_is_authorized執行vp->v_authorized_actions & action,來判斷是否命中上次cache,之後在執行完權限檢查後,通過調用vnode_cache_authorized_action執行vp->v_authorized_actions |= action更新cache。

Xnu將文件系統的acl評估機制也封裝到了kauth子系統裏。

bsd/sys/kauth.h

struct kauth_acl {
        u_int32_t       acl_entrycount;
        u_int32_t       acl_flags;
        struct kauth_ace acl_ace[1];
};

acl_entrycount表示的是struct kauth_ace acl_ace數組的大小。

struct kauth_ace {
        guid_t          ace_applicable;
        u_int32_t       ace_flags;
        kauth_ace_rights_t ace_rights;          
};

一個acl entry定義爲struct kauth_ace結構,acl評估的業界通用算法就是從前到後,依次對比每個ace項,如果權限匹配爲deny,則直接返回失敗,否則進行下一個ace匹配。

在函數vnode_authorize_simple裏判斷vnode結構的acl鏈表是否爲空,如果不爲空則調用kauth_acl_evaluate進行acl權限檢查。


Paper 本文由 Seebug Paper 發佈,如需轉載請註明來源。本文地址:https://paper.seebug.org/1472/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章