Linux cred内核credential与commit_creds切换
struct cred是Linux内核中管理进程凭证的核心数据结构,位于include/linux/cred.h。每个进程的task_struct中维护了两个cred指针:real_cred和cred。real_cred用于可执行文件访问权限的底层凭证,cred是当前系统调用使用的有效凭证。这种双指针设计允许set*id系列系统调用在提交新凭证之前先进行权限验证。
struct cred完整定义如下:
```c
struct cred {
atomic_t usage;
kuid_t uid; /* 真实UID */
kgid_t gid; /* 真实GID */
kuid_t suid; /* 保存的UID */
kgid_t sgid; /* 保存的GID */
kuid_t euid; /* 有效UID */
kgid_t egid; /* 有效GID */
kuid_t fsuid; /* VFS操作使用的UID */
kgid_t fsgid; /* VFS操作使用的GID */
unsigned securebits; /* 安全位掩码 */
kernel_cap_t cap_inheritable; /* 可继承capability */
kernel_cap_t cap_permitted; /* 允许的capability */
kernel_cap_t cap_effective; /* 有效的capability */
kernel_cap_t cap_bset; /* capability边界集 */
kernel_cap_t cap_ambient; /* ambient capability */
unsigned char jit_keyring;
struct key __rcu *session_keyring;
struct key __rcu *process_keyring;
struct key __rcu *thread_keyring;
struct key __rcu *request_key_auth;
#ifdef CONFIG_SECURITY
void *security; /* LSM模块私有数据指针 */
#endif
struct user_struct *user;
struct group_info *group_info;
struct rcu_head rcu; /* RCU回调,用于延迟释放 */
} __fastpath_aligned;
```
usage字段是原子计数,控制cred对象的生命周期。当usage降为零时触发RCU回调释放内存。prepare_creds()从current->cred复制一份副本,返回的新cred对象usage初始化为1:
```c
struct cred *prepare_creds(void)
{
struct task_struct *task = current;
const struct cred *old;
struct cred *new;
new = kmem_cache_alloc(cred_jar, GFP_KERNEL);
if (!new)
return NULL;
old = task->cred;
memcpy(new, old, sizeof(struct cred));
atomic_set(&new->usage, 1);
/* 增加各个子对象的引用计数 */
get_uid(new->user);
get_group_info(new->group_info);
get_cred(new->ucred);
/* 非原子地设置新旧security blob */
if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
goto error;
validate_creds(new);
return new;
error:
put_cred(new);
return NULL;
}
```
commit_creds()执行最终的凭证切换,这是整个cred机制的核心路径。它用RCU指针赋值替换task中的凭证指针,确保正在运行的其它CPU看到一致的cred视图:
```c
int commit_creds(struct cred *new)
{
struct task_struct *task = current;
const struct cred *old = task->real_cred;
/* new必须是通过prepare_creds分配的有效对象 */
BUG_ON(atomic_read(&new->usage) < 1);
BUG_ON(!same_cred(task->real_cred, task->cred));
/* 执行审计和安全框架的hook */
security_cred_commit(new, old);
audit_log_task_set_cred(task, new);
/* RCU同步赋值,保证cred指针切换的原子可见性 */
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
/* 递减旧cred引用计数 */
if (old)
put_cred(old);
return 0;
}
```
put_cred()递减引用计数,当检测到usage降为零时通过call_rcu注册延迟释放回调:
```c
void put_cred(const struct cred *cred)
{
if (atomic_dec_and_test(&(cred)->usage))
call_rcu(&cred->rcu, put_cred_rcu);
}
static void put_cred_rcu(struct rcu_head *rcu)
{
struct cred *cred = container_of(rcu, struct cred, rcu);
/* 安全模块释放私有数据 */
if (cred->security)
security_cred_free(cred);
/* 释放子对象引用 */
free_uid(cred->user);
put_group_info(cred->group_info);
put_cred_rcu(cred->ucred);
/* 返回slab缓存 */
kmem_cache_free(cred_jar, cred);
}
```
override_creds()和revert_creds()用于内核线程临时切换凭证执行特定操作,典型的场景是VFS层打开文件时临时获取文件系统的权限:
```c
const struct cred *override_creds(const struct cred *overriden)
{
const struct cred *old = current->cred;
rcu_assign_pointer(current->cred, overriden);
return old;
}
void revert_creds(const struct cred *old)
{
const struct cred *overridden = current->cred;
rcu_assign_pointer(current->cred, old);
put_cred(overridden);
}
```
cred对象通过slab缓存cred_jar分配,配合RCU机制实现了高性能的凭证切换路径。每次execve()、setuid()、capset()系统调用都会经过prepare_creds修改特定字段然后commit_creds提交,这保证了凭证修改的原子性和一致性。
Linux cred内核credential与commit_creds切换