Credentials, capabilities, user namespaces, and authority

Trace credentials, capability checks and user-namespace authority.

14 min readChapter 73 of 168Advanced
In this chapter

Before you start#

Review tasks and process lifetime, RCU and references, and Rust ownership. This chapter reads upstream Linux v6.12, a historical baseline, not a current security recommendation. UID values and permissions observed on a distribution kernel may differ because of configuration and backports. The walkthrough is source reasoning; the observation lab does not change host credentials.

Learning goals#

Distinguish a task's subjective and objective credentials, follow prepare/commit/override/revert ownership, calculate a constrained capability transition across exec, and explain why an open descriptor can remain useful after a credential change.

Source walkthrough#

Credentials, capabilities, user namespaces, and authority
  1. Prepare

    Copy credentials and acquire referenced objects; abort on error

  2. Commit

    Publish subjective and objective credentials with RCU

  3. Override

    Temporarily replace subjective credentials and retain restore token

  4. Revert

    Restore the saved subjective credentials and put the override

Conceptual model. Conceptual source-based model, not a captured execution; actual checks, configuration and policy vary.

Linux schedules tasks, commonly called threads. A process is a useful userspace shorthand, but the kernel credential pointers belong to each task_struct; applications and threading libraries normally coordinate credential changes so their threads have consistent identities. Do not assume a credential change in one kernel task automatically modified every other task. Each task has cred for its current subjective credentials, used when it acts, and real_cred for its objective credentials, used when another task inspects it. Usually they refer to the same immutable, reference-counted struct cred. A temporary override changes the subjective pointer while leaving the objective pointer in place.

The structure includes real, effective, saved and filesystem UIDs and GIDs; supplementary groups; a user namespace; capability sets; securebits; and references to supporting objects. Real IDs commonly describe the original identity, effective IDs are used by many privilege decisions, saved IDs support controlled changes, and filesystem IDs participate in filesystem permission checks. Actual access decisions can also involve ACLs, mount idmaps, capabilities and LSM policy. Supplementary groups can grant group-based access beyond the primary GID. These names are not interchangeable: seeing effective UID zero in a container does not establish authority in the initial user namespace.

The struct cred declaration and credentials documentation describe the object. A credential's contents are treated as immutable once committed. Changes are prepared in a new object and published, rather than editing a credential another reader might hold. RCU protects pointer observation; a reader that needs to keep an object beyond the appropriate protected region needs the relevant reference. RCU is not permission to mutate an old credential or use an unreferenced pointer indefinitely.

Prepare, publish and unwind

prepare_creds allocates a new credential, copies the current subjective one, initializes its usage count, and acquires references for groups, user, namespace and configured keyrings. It then obtains accounting and security data. This exact short excerpt is from prepare_creds, Linux v6.12, GPL-2.0-or-later, copyright Red Hat and contributors; surrounding lines are omitted:

c
	if (security_prepare_creds(new, old, GFP_KERNEL_ACCOUNT) < 0)
		goto error;

	return new;

error:
	abort_creds(new);
	return NULL;

The security hook can reject or fail preparation. Success returns the new, still-uncommitted object to the caller. Failure jumps to the cleanup label, calls abort_creds to drop the prepared object's resources and returns NULL. A caller must handle that null result; it cannot publish a half-prepared object. GFP_KERNEL_ACCOUNT also indicates an allocation context that can sleep, so this is not an arbitrary hard-interrupt operation.

The failure branch is abort_creds in the same kernel/cred.c, v6.12, SPDX GPL-2.0-or-later (exact, unabridged):

c
void abort_creds(struct cred *new)
{
	kdebug("abort_creds(%p{%ld})", new,
	       atomic_long_read(&new->usage));

	BUG_ON(atomic_long_read(&new->usage) < 1);
	put_cred(new);
}

The function drops the prepared object without returning a status, and its BUG_ON asserts that a reference still exists. The excerpt does not show prepare_creds itself, so it does not show which allocation or hook failed.

commit_creds requires the current subjective credential to match the old objective credential; the source uses a BUG_ON to enforce that precondition. It acquires an additional reference for the new subjective pointer, updates dumpability and accounting as required, and publishes new real_cred and cred pointers with RCU assignment. It later releases old references. Its write barrier before publication in relevant privilege changes orders dumpability against credential visibility to prevent a ptrace race described in the source. An old credential remains alive while references or RCU readers require it; publishing a new pointer does not immediately free the old object.

override_creds(new) is a different operation: it gets a reference to the override, installs it as current cred and returns the old subjective pointer to the caller. revert_creds(old) restores that old pointer and puts the override. The old pointer returned is the token for the matching revert, not an invitation to free it independently. Error exits after an override must still revert it. Calling commit while the override is active violates the commit precondition. This distinction matters for kernel code temporarily acting under a different credential: the task's objective identity remains different from its temporary subjective authority.

The matching revert is revert_creds in kernel/cred.c, v6.12, SPDX GPL-2.0-or-later (exact, unabridged):

c
void revert_creds(const struct cred *old)
{
	const struct cred *override = current->cred;

	kdebug("revert_creds(%p{%ld})", old,
	       atomic_long_read(&old->usage));

	rcu_assign_pointer(current->cred, old);
	put_cred(override);
}

The restored pointer is the token returned by override_creds, and the override reference is put in the same function. The excerpt does not show how a caller reaches it on each error path, which is the step a review must check.

Credential prepare, commit and override
  1. prepare_creds

    Copy the subjective cred into a new object that is not yet visible

  2. Preparation fails

    A security hook error calls abort_creds and returns NULL to the caller

  3. commit_creds

    Publish new real_cred and cred pointers; old references are released later

  4. override_creds

    Install a temporary cred as cred and return the old pointer as the token

  5. revert_creds

    Restore the saved pointer and put the override reference

Source map. Linux v6.12 source map of kernel/cred.c ownership transitions; a teaching model of the call order, not a captured run.

Capability sets and namespace scope#

A capability divides privileged operations into named units. The permitted set bounds capabilities that can become effective; effective capabilities participate in many immediate checks; inheritable participates in exec transitions; the bounding set limits the file-permitted contribution on exec; and ambient capabilities can survive suitable unprivileged execs subject to the invariant that they also be permitted and inheritable. Securebits modify selected UID-zero and capability-transition semantics; their locked variants restrict later changes. no_new_privs is a per-thread attribute outside the struct cred fields: once set, it is inherited across fork and exec and cannot be cleared by that thread. It constrains privilege gains through exec but does not erase authority already held.

capable(cap) asks about a capability in the initial user namespace through the kernel's capability machinery. ns_capable(user_ns, cap) asks relative to the specified target user namespace; namespace ancestry and ownership rules affect the result. Different resources are governed by different user namespaces. UID 0 inside a child user namespace can administer resources owned by that namespace when the relevant checks permit it, yet lacks automatic capability in the initial namespace. An ID mapping relates namespace IDs to parent IDs; it does not make an unmapped host object owned by namespace root or bypass other policy. Read the actual target namespace at each check rather than describing a capability as universally “root.”

For a task with effective capability set empty, a capability in permitted alone does not necessarily satisfy an immediate effective check. A task may be allowed to raise an effective bit from permitted under the relevant rules, but that is a separate operation. The kernel's capability code and common capability hooks are the correct baseline for a specific decision. CAP_SYS_ADMIN covers many unrelated operations; its presence is broad, and the capability name alone does not document every check or namespace relationship.

Exec: a conditional calculation#

execve constructs proposed credentials for the new image. Under the ordinary non-root, no-unsafe-exec special-case assumptions below, use old inheritable I, bounding B and ambient A, plus file inheritable FI, file permitted FP and file effective flag FE. The source computes the file contribution (B & FP) | (I & FI) and later adds the new ambient set. For a privileged executable with file capabilities or a set-ID transition, the source clears ambient. The resulting effective set is the resulting permitted set when FE is set, otherwise the new ambient set. Real UID-zero handling, securebits, file capability version and namespace mapping, mount options, ptrace, no_new_privs, and LSM policy can alter or prevent a transition. This example deliberately excludes those special cases and is not a universal exec formula.

Let the old task have I = {CAP_NET_BIND_SERVICE}, B = {CAP_NET_BIND_SERVICE, CAP_CHOWN}, and ambient = empty. A file has FP = {CAP_NET_BIND_SERVICE}, FI = empty and FE set. If the file capability is accepted and no other restriction applies, the file contribution and new permitted set contain CAP_NET_BIND_SERVICE; the new effective set contains it too. It grants a specific capability in the relevant namespace, not all privileges. Now remove that bit from B. The file contribution becomes empty; the source's file-capability handling can return -EPERM for an effective file capability whose required file-permitted bits cannot be obtained. Do not claim that every missing bounding bit merely produces a successful exec with less privilege.

The later part of cap_bprm_creds_from_file contains this exact v6.12 GPL-2.0-or-later excerpt; surrounding branches and comments are omitted:

Simplified exec capability calculation
  1. File contribution

    Compute (B & FP) | (I & FI) from the bounding, inheritable and file sets

  2. Clear ambient

    Applies when file capabilities are accepted or a set-ID transition is involved

  3. Add ambient

    Remaining ambient bits join the new permitted set under their invariant

  4. Effective set

    Equals the new permitted set when the file effective flag is set; otherwise the new ambient set

Source map. Simplified Linux v6.12 exec capability model with root, securebits and LSM cases omitted; a teaching model, not a universal formula.
c
	if (has_fcap || is_setid)
		cap_clear(new->cap_ambient);

The condition asks whether accepted file capabilities exist or a set-ID transition is involved; the statement clears the new ambient set in that case. The subsequent code combines the new ambient set into permitted and chooses the effective set based on the file effective flag. Separately, the unsafe/no-new-privileges branch can intersect new permitted with old permitted and reset effective IDs under its conditions. This shows why applying the simplified calculation without its assumptions is unsafe.

For a nonprivileged executable without file capabilities or set-ID transition, ambient bits satisfying their invariant can be carried into the new permitted and effective sets. Ambient is not a free mechanism to manufacture a capability outside permitted and inheritable. For a setuid executable, filesystem and mount conditions, tracing and no_new_privs influence whether the requested identity change has effect. Source-level presence of a setuid bit alone is not evidence of a successful privilege gain.

Dropping privilege and residual authority#

A privilege drop should identify the future workload's needed IDs, groups, capabilities, file descriptors and namespace context. Dropping an effective bit without considering the permitted, saved or ambient state may leave a route to regain authority under applicable rules. Conversely, destroying an authority path prematurely can prevent necessary cleanup. Kernel checks are operation-specific; privilege-dropping order and application design require careful review and testing in a disposable environment.

An already opened file descriptor is an important counterexample to “UID changed, therefore access is gone.” A process may still have an open file or socket, and operations on it have their own permission and LSM checks. Some decisions were made at open time, while others are checked later against current credentials, file credentials or object state. Passing an fd transfers a reference to an object, not an automatic bypass of every later authorization check. Account for descriptors inherited across exec, close-on-exec settings, existing mappings and IPC peers when defining what authority remains.

Credential changes in a running service#

Suppose a server opens a listening socket, then plans to run request handling under a less privileged identity. First distinguish the process that owns the listening descriptor from any child tasks and consider what other descriptors will be inherited. The server may close unrelated descriptors, decide whether the listener must remain open, set appropriate group and identity state using supported userspace APIs, and reduce capabilities according to its documented design. Merely changing the displayed effective UID does not answer whether a saved UID, supplementary group, effective or permitted capability, or open directory descriptor still enables a sensitive operation. Nor does it say whether an LSM label permits the operation.

The kernel can use file credentials for work associated with an open file while other checks use the current task's credentials. A review therefore asks which specific operation uses which credential and whether a temporary override applies. A filesystem worker cannot safely save current->cred as an unreferenced pointer for later asynchronous work; it must follow the relevant get/put and override contract. If an allocation or security hook fails while preparing a new credential, the service's proposed new identity has not been committed by that preparation path. The caller must choose an error response; returning to request processing as though the drop succeeded could expose the old authority.

For an exec calculation with an ordinary unprivileged file and no file capabilities, suppose permitted and inheritable both include CAP_NET_BIND_SERVICE, ambient also contains it, and no other special transition applies. The ambient invariant is satisfied, so the bit can contribute to the new permitted and effective sets. Contrast the earlier file-capability case: that file clears the new ambient set and computes its file contribution separately. This comparison is conditional on the executing task's state and applicable policy. Neither example asserts that the observation shell has the capability or that an application should retain it. A real deployment should test credential transitions and failure handling in an isolated child or disposable guest before relying on a least-privilege claim.

Practice#

Read-only Linux observation lab

Run on your own authorized Linux host or in a disposable guest. This reads the observing shell process's procfs entries and does not change credentials or create a namespace:

sh
printf 'kernel: '; uname -r
printf 'shell pid: %s\n' "$$"
for f in status uid_map gid_map; do
  echo "--- /proc/$$/$f ---"
  if [ "$f" = status ]; then sed -n '/^Uid:/p;/^Gid:/p;/^Groups:/p;/^Cap/p;/^NoNewPrivs:/p' "/proc/$$/$f"; else cat "/proc/$$/$f"; fi
done
readlink "/proc/$$/ns/user" 2>/dev/null || true

The four numbers in Uid: and Gid: are real, effective, saved and filesystem IDs in that order. Capability masks are hexadecimal bitsets; if capsh --decode=MASK is installed, decoding a copied mask can name bits, but it does not reveal which target namespace a particular future operation checks. uid_map and gid_map list mapping ranges to the parent namespace. An empty or inaccessible proc entry, restricted namespace information, missing capability fields, or a container policy is an observation limit; do not use sudo or alter mappings to make the lab work. Record the actual kernel and whether it matches upstream v6.12.

Source-only alternative: use the supplied exec sets above to calculate the accepted and bounding-bit-removed cases. Write the old and new permitted/effective/ambient sets, state each simplifying assumption, and identify why the second case may return -EPERM. Add a separate reference timeline for prepare failure and override/revert. These are source-derived cases, not a claim that a privileged executable or namespace was run. Deliver the observed status and mapping table or the source tables, with an explicit remaining authority such as an already open descriptor.

Knowledge check#

  1. Why must an error after override_creds still call revert_creds?
  2. Does UID 0 in a child user namespace satisfy every initial-user-namespace capability check?
  3. In the worked file-capability example, why can removing the permitted file bit from the bounding set cause -EPERM?
  4. Can a file descriptor remain useful after an effective UID drop, and does it bypass every later check?
  5. What prevents an RCU reader from safely retaining a raw old credential pointer forever?

Answers#

  1. The subjective credential remains overridden until reverted. The matching revert restores the saved pointer and releases the override reference, including on failure paths.
  2. No. The capability check is relative to its target user namespace and the resource's ownership context. Child namespace authority does not automatically extend to the initial namespace.
  3. The calculated permitted set lacks a file-permitted capability that an effective file-capability executable requires; the cited helper returns -EPERM under those conditions. Other exec restrictions and special root rules still matter.
  4. Yes, the open object can remain referenced, but individual later operations may perform current-credential, file-credential or LSM checks. An fd is authority to attempt operations, not universal authorization.
  5. RCU covers a bounded read-side lifetime. A reader needing longer access must use the appropriate reference mechanism, and the old object can be reclaimed once references and grace-period requirements are satisfied.

Checkpoint#

Produce the exec capability table and a credential-reference timeline including failed preparation, successful commit and temporary override. Ready means the namespace target, ambient clearing, error cleanup and residual fd authority are all identified without claiming an unrun privilege change. Re-read the two source functions and retry the table if any reference or set transition is unexplained. Progress is not certification.

Sources#

A little further beneath the surface.

Check your understanding with the checkpoint. Marking a chapter as read saves a bookmark, not a proficiency assessment.