Linux內核源碼分析 -- 構造新 cred -- prepare_kernel_cred


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 函數

image-20210223184547478

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_structreal_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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM