no_new_privs Makes Exec-Time Privilege Gain Irreversible

A Linux service may deliberately execute programs that carry set-user-ID bits or file capabilities while intending to remain at its existing privilege level. Without an explicit execution boundary, execve() can be a privilege transition: metadata on the executable may change effective credentials or contribute capabilities to the new program.

The no_new_privs task attribute changes that transition. Once set, a successful execve() cannot grant the task privilege that it could not exercise before the call. The attribute is inherited by descendants, survives execution, and cannot be cleared. Those properties make it a one-way constraint on a process lineage rather than a temporary option around one executable.

The boundary is attached to execve

Linux normally evaluates several privilege mechanisms while replacing a process image. Set-user-ID and set-group-ID mode bits can alter effective IDs, and file capabilities can contribute capabilities according to the capability transformation rules.

With no_new_privs set, those executable-file mechanisms cannot elevate the task through execve(). The kernel documentation also states that Linux Security Modules do not relax constraints after execution in this mode.

The operation is commonly established with prctl():

if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
    /* handle error */
}

The call changes the calling thread’s attribute. A value other than the supported setting is not a request for a reversible mode; there is no operation that turns the bit back off after it becomes active.

This makes placement significant. A launcher that sets the attribute before executing an untrusted workload commits that thread and its future execution lineage to the restricted transition semantics.

Inheritance turns a local decision into lineage policy

The attribute is inherited across fork() and clone() and preserved across execve(). A child therefore cannot recover ordinary exec-time privilege transitions merely by replacing its process image.

launcher
  |
  +-- set no_new_privs
  |
  +-- fork
       |
       +-- execve helper
            |
            +-- execve another program

all descendants retain the attribute

This persistence is useful for sandbox launchers because policy does not depend on every later executable repeating the setup call. It also means the decision can affect software several execution steps away from the component that set it.

The kernel exposes the state through the NoNewPrivs field in /proc/<pid>/status on kernels that provide that field. That observation reports the task attribute; it does not describe every other confinement mechanism that may apply to the process.

It is not a general privilege drop

The name can suggest a broader guarantee than the interface provides. The restriction is centered on privilege gained through execve(). It does not by itself remove credentials, close file descriptors, revoke capabilities already held, constrain filesystem access, or filter system calls.

A task that already has authority before setting the bit may retain that authority afterward. For example, an open file descriptor remains governed by normal descriptor and object permission semantics. Likewise, a process with suitable existing privilege may perform operations that do not depend on an exec-time elevation.

This distinction separates two security operations:

no_new_privs       -> blocks privilege gain caused by execve
privilege dropping -> removes authority the task already possesses

A robust launcher may need both, plus separate isolation controls. Treating the bit as a complete sandbox would leave unrelated authority channels untouched.

Seccomp uses the boundary to prevent privileged filter inheritance

Seccomp filter mode can persist filters across execve() when execution is permitted by the filter. That persistence creates a security issue if an unprivileged task could install an arbitrary filter and then execute a program that gains privilege.

Linux therefore requires an unprivileged thread to have no_new_privs set before installing a seccomp filter, unless it has CAP_SYS_ADMIN in its user namespace. The condition prevents the thread from arranging attacker-selected syscall behavior and then carrying that behavior into a newly privileged executable.

The relationship is structural rather than synonymous. no_new_privs controls the privilege transition; seccomp controls selected system-call behavior. Setting the first does not install a syscall filter, and installing a filter does not make the first redundant for an unprivileged setup path.

Security-module transitions need deployment-specific review

The exec boundary also interacts with Linux Security Modules. Kernel documentation notes that in no_new_privs mode an LSM may be prevented from relaxing constraints across execution, but the same mechanism can also interfere with LSM transitions that would otherwise tighten confinement.

That consequence matters for generic service launchers. Setting the bit globally can alter an execution policy that expected a domain transition at execve(). The safe deployment question is therefore not only whether privilege gain should be blocked, but also whether the surrounding LSM policy expects an execution-time label or domain change.

This is an implementation and policy boundary, not a reason to avoid the attribute. It means the launcher and the active security module must agree on the intended transition semantics.

Irreversibility reduces later policy ambiguity

A reversible process flag could be cleared immediately before a sensitive execution, moving the trust decision to whichever component retained permission to clear it. no_new_privs deliberately avoids that shape. Once active, later code cannot restore ordinary exec-time elevation for that lineage.

The result is a monotonic property: execution can continue, process images can change, and children can be created, but the ability to acquire added privilege from executable metadata does not return. That property is narrower than sandboxing, yet stronger than a convention that each child must voluntarily avoid privileged executables.

For process supervisors, the practical boundary is precise. Set the attribute only after any required privileged execution transitions are complete, and before handing control to code whose descendants must never regain such transitions. The security effect comes from the kernel preserving that one-way state across the execution boundary.