cred: clarify that task_struct::cred is only for the current task

The `cred` field in task_struct is currently marked as __rcu, but that's
not true: It can point to credentials from access_override_creds(), which
do not get freed with RCU delay.

What actually protects task_struct::cred is that accessing it is only
permitted for the current task (except for setting up a child during fork()
or tearing down a dead process).
(There is currently code in Smack that violates this rule, but that's a bug
and causes UAF, I have sent a separate fix for that.)

Clarify this, remove the __rcu marker, and remove RCU helpers from all
accesses to this field.

Signed-off-by: Jann Horn <jannh@google.com>
Reviewed-by: Serge Hallyn <serge@hallyn.com>
[PM: style tweak in security_init(), applied fixup from JH]
Signed-off-by: Paul Moore <paul@paul-moore.com>
This commit is contained in:
Jann Horn 2026-08-06 22:15:54 +02:00 committed by Paul Moore
parent cee9395acd
commit b030ffaedc
6 changed files with 23 additions and 14 deletions

View File

@ -180,12 +180,18 @@ static inline bool cap_ambient_invariant_ok(const struct cred *cred)
static inline const struct cred *override_creds(const struct cred *override_cred)
{
return rcu_replace_pointer(current->cred, override_cred, 1);
const struct cred *old = current->cred;
current->cred = override_cred;
return old;
}
static inline const struct cred *revert_creds(const struct cred *revert_cred)
{
return rcu_replace_pointer(current->cred, revert_cred, 1);
const struct cred *override_cred = current->cred;
current->cred = revert_cred;
return override_cred;
}
DEFINE_CLASS(override_creds,
@ -293,11 +299,10 @@ DEFINE_FREE(put_cred, struct cred *, if (!IS_ERR_OR_NULL(_T)) put_cred(_T))
/**
* current_cred - Access the current task's subjective credentials
*
* Access the subjective credentials of the current task. RCU-safe,
* since nobody else can modify it.
* Access the subjective credentials of the current task.
* Nobody else can modify it.
*/
#define current_cred() \
rcu_dereference_protected(current->cred, 1)
#define current_cred() (current->cred)
/**
* current_real_cred - Access the current task's objective credentials

View File

@ -1172,8 +1172,12 @@ struct task_struct {
/* Objective and real subjective task credentials (COW): */
const struct cred __rcu *real_cred;
/* Effective (overridable) subjective task credentials (COW): */
const struct cred __rcu *cred;
/*
* Effective (overridable) subjective task credentials (COW).
* Only accessible for the current task and during task creation/freeing.
* This pointer is not managed by RCU!
*/
const struct cred *cred;
#ifdef CONFIG_KEYS
/* Cached requested key. */

View File

@ -160,7 +160,7 @@ struct task_struct init_task __aligned(L1_CACHE_BYTES) = {
.sibling = LIST_HEAD_INIT(init_task.sibling),
.group_leader = &init_task,
RCU_POINTER_INITIALIZER(real_cred, &init_cred),
RCU_POINTER_INITIALIZER(cred, &init_cred),
.cred = &init_cred,
.comm = INIT_TASK_COMM,
.thread = INIT_THREAD,
.real_fs = &init_fs,

View File

@ -459,7 +459,7 @@ static int audit_field_compare(struct task_struct *tsk,
*
* If task_creation is true, this is an explicit indication that we are
* filtering a task rule at task creation time. This and tsk == current are
* the only situations where tsk->cred may be accessed without an rcu read lock.
* the only situations where tsk->cred may be accessed.
*/
static int audit_filter_rules(struct task_struct *tsk,
struct audit_krule *rule,
@ -476,7 +476,8 @@ static int audit_filter_rules(struct task_struct *tsk,
if (ctx && rule->prio <= ctx->prio)
return 0;
cred = rcu_dereference_check(tsk->cred, tsk == current || task_creation);
WARN_ON(tsk != current && !task_creation);
cred = tsk->cred;
for (i = 0; i < rule->field_count; i++) {
struct audit_field *f = &rule->fields[i];

View File

@ -414,7 +414,7 @@ int commit_creds(struct cred *new)
inc_rlimit_ucounts(new->ucounts, UCOUNT_RLIMIT_NPROC, 1);
rcu_assign_pointer(task->real_cred, new);
rcu_assign_pointer(task->cred, new);
task->cred = new;
if (new->user != old->user || new->user_ns != old->user_ns)
dec_rlimit_ucounts(old->ucounts, UCOUNT_RLIMIT_NPROC, 1);
if (new->user_ns != old->user_ns)

View File

@ -476,8 +476,7 @@ int __init security_init(void)
blob_sizes.lbs_inode, 0,
SLAB_PANIC, NULL);
if (lsm_cred_alloc((struct cred *)unrcu_pointer(current->cred),
GFP_KERNEL))
if (lsm_cred_alloc((struct cred *)current->cred, GFP_KERNEL))
panic("early LSM cred alloc failed\n");
if (lsm_task_alloc(current))
panic("early LSM task alloc failed\n");