Linux内核源码分析 -- 构造新 cred -- prepare_kernel_cred

时间:2021-02-23
本文章向大家介绍Linux内核源码分析 -- 构造新 cred -- prepare_kernel_cred,主要包括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 函数

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

原文地址:https://www.cnblogs.com/crybaby/p/14437651.html