prepare_kernel_cred - Prepare a set of credentials for a kernel service
使用指定進程的 real_cred 去構造一個新的 cred,不是引用,不是引用,不是引用,而是創建一個新的 cred
源碼版本:Linux Kernel 5.9.9
prepare_kernel_cred
/**
* prepare_kernel_cred - Prepare a set of credentials for a kernel service
* @daemon: A userspace daemon to be used as a reference
*
* Prepare a set of credentials for a kernel service. This can then be used to
* override a task's own credentials so that work can be done on behalf of that
* task that requires a different subjective context.
*
* @daemon is used to provide a base for the security record, but can be NULL.
* If @daemon is supplied, then the security data will be derived from that;
* otherwise they'll be set to 0 and no groups, full capabilities and no keys.
*
* The caller may change these controls afterwards if desired.
*
* Returns the new credentials or NULL if out of memory.
*/
struct cred *prepare_kernel_cred(struct task_struct *daemon)
{
const struct cred *old;
struct cred *new;
// 從 cred_jar 中分配一個 cred
//(cred_jar 是一個 kmem_cache ,每次 釋放一個無用的 cred 的時候不會直接釋放占用的內存 而是放入 cred_jar,高頻使用的數據結構都有這樣一個緩存機制 詳細可以自己查查)
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
// 分配失敗的話直接 return
if (!new)
return NULL;
kdebug("prepare_kernel_cred() alloc %p", new);
// 如果有指定進程,也就是參數 deamon
if (daemon)
old = get_task_cred(daemon); // 獲取參數 daemon 進程(我說的是參數,不是真的 daemon 進程)的 real_cred
else
old = get_cred(&init_cred); // 沒有指定進程的話,也就是傳入參數 0,直接使用 init 進程的 cred
// 驗證 old cred 的 magic,看 cred 是否可用 (ord->magic == CRED_MAGIC)
validate_creds(old);
*new = *old; // 拷貝內容,把 old 的各個字段的值賦值給 new cred(對指針不了解的人可能迷惑,這里對指針解引用了)
// 不設置 rcu 變量銷毀時調用的 hook 函數
new->non_rcu = 0;
// 設置使用 new cred 的進程數為 1
atomic_set(&new->usage, 1);
// 訂閱進程數為 0
set_cred_subscribers(new, 0);
// 初始化 group_info,usage 加 1 。(atomic_inc(&gi->usage);)
get_group_info(new->group_info);
// 初始化 user_struct,__count 加 1。(refcount_inc(&u->__count);)
get_uid(new->user);
// 這里好像啥也不做,get_user_ns 返回 init 進程的 user_namespace
get_user_ns(new->user_ns);
// 啟用 keyring 的話,初始化
#ifdef CONFIG_KEYS
new->session_keyring = NULL;
new->process_keyring = NULL;
new->thread_keyring = NULL;
new->request_key_auth = NULL;
new->jit_keyring = KEY_REQKEY_DEFL_THREAD_KEYRING;
#endif
// LSM 相關,不想詳細講,無非就是使用了 LSM 它自己的 hook 函數
#ifdef CONFIG_SECURITY
new->security = NULL;
#endif
// 這里的 security_prepare_creds 最終會調用 LSM 自己的 hook 函數
if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
goto error;
put_cred(old); // old->usage - 1,如果減 1 后為 0 則銷毀 cred
validate_creds(new); // 驗證 new cred 的 magic,看 cred 是否可用 (ord->magic == CRED_MAGIC)
return new; // 返回 new cred,結束
error:
put_cred(new);
put_cred(old);
return NULL;
}
EXPORT_SYMBOL(prepare_kernel_cred);
get_task_cred
/**
* get_task_cred - Get another task's objective credentials
* @task: The task to query
*
* Get the objective credentials of a task, pinning them so that they can't go
* away. Accessing a task's credentials directly is not permitted.
*
* The caller must also make sure task doesn't get deleted, either by holding a
* ref on task or by holding tasklist_lock to prevent it from being unlinked.
*/
const struct cred *get_task_cred(struct task_struct *task)
{
const struct cred *cred;
// 上 rcu 讀取鎖
rcu_read_lock();
do {
// 讀取 task 的 real_cred
// 因為 real_cred 是 rcu 變量需要持有鎖
// 然后使用 rcu_dereference 去讀取
cred = __task_cred((task));
BUG_ON(!cred);
} while (!get_cred_rcu(cred));
rcu_read_unlock();
return cred;
}
EXPORT_SYMBOL(get_task_cred);
__task_cred
/**
* __task_cred - Access a task's objective credentials
* @task: The task to query
*
* Access the objective credentials of a task. The caller must hold the RCU
* readlock.
*
* The result of this function should not be passed directly to get_cred();
* rather get_task_cred() should be used instead.
*/
// 因為 real_cred 是 rcu 變量需要持有鎖
// 然后使用 rcu_dereference 去讀取
#define __task_cred(task) \
rcu_dereference((task)->real_cred)
security_prepare_creds
int security_prepare_creds(struct cred *new, const struct cred *old, gfp_t gfp)
{
int rc = lsm_cred_alloc(new, gfp);
if (rc)
return rc;
// 調用 Linux Security Module hook function 里面對應的 cred_prepare 的 cred_prepare 函數
// 其實這里 LSM 有幾個分支,比如 apparmor, tomoyo, bpf....等等
// https://www.kernel.org/doc/html/latest/admin-guide/LSM/index.html
rc = call_int_hook(cred_prepare, 0, new, old, gfp);
if (unlikely(rc))
security_cred_free(new);
return rc;
}
關於 LSM 有以下幾種
這里講 prepare_kernel_cred, LSM 又是別的內容了,不展開講了,可以去下面這些文件里面看看具體的 hook 函數
apparmor 的 cred_prepare 的 hook 函數
LSM_HOOK_INIT(cred_prepare, apparmor_cred_prepare)
/*
* prepare new cred label for modification by prepare_cred block
*/
static int apparmor_cred_prepare(struct cred *new, const struct cred *old,
gfp_t gfp)
{
set_cred_label(new, aa_get_newest_label(cred_label(old)));
return 0;
}
tomoyo 的 cred_prepare 的 hook 函數
/**
* tomoyo_cred_prepare - Target for security_prepare_creds().
*
* @new: Pointer to "struct cred".
* @old: Pointer to "struct cred".
* @gfp: Memory allocation flags.
*
* Returns 0.
*/
static int tomoyo_cred_prepare(struct cred *new, const struct cred *old,
gfp_t gfp)
{
/* Restore old_domain_info saved by previous execve() request. */
struct tomoyo_task *s = tomoyo_task(current);
if (s->old_domain_info && !current->in_execve) {
atomic_dec(&s->domain_info->users);
s->domain_info = s->old_domain_info;
s->old_domain_info = NULL;
}
return 0;
}
。。。。。。
總結
正常調用 prepare_kernel_cred
會兩種情況
- 一種是 調用
prepare_kernel_cred
的時候給一個有效的task_struct
地址,這樣的話會使用這個task_struct
的real_cred
作為模板去復制一個新的 cred - 還有一種情況就是
prepare_kernel_cred(0)
,這個情況下prepare_kernel_cred
會使用init
進程的cred
作為模板復制出一個新的cred
結束!
# scriptkid @ MacBook-Pro in ~ [19:01:34]
$ date
2021年 2月23日 星期二 19時01分35秒 CST