setpriv: The util-linux One-Shot Privilege Manipulator You've Been Reinventing in C

2026-07-12

You need to run a program as a specific UID with a specific set of capabilities, no more, no less — plus no_new_privs, an empty supplementary group list, and a parent-death signal so it dies with the shell. Do you write a C wrapper with libcap, prctl, and setresuid? Reach for sudo -u and pray about the environment? Drop in a systemd unit for a one-shot?

setpriv ships with util-linux — meaning it's already on every Linux box you've SSH'd into this year — and does exactly one thing: execve a program with a precisely specified privilege set. No PTY allocation, no PAM stack, no sudoers.conf, no login-shell surprises.

Inspect what you have. Before you drop, look:

$ setpriv --dump
uid: 0
euid: 0
Supplementary groups: [none]
no_new_privs: 0
Inheritable capabilities: [none]
Ambient capabilities: [none]
Capability bounding set: chown,dac_override,dac_read_search,fowner,...
Securebits: [none]

Drop to a user, cleanly. Compare this to sudo -u nobody, which forks a shell, sanitizes environment, and traverses PAM:

setpriv --reuid=nobody --regid=nogroup --clear-groups \
        --no-new-privs \
        -- /usr/local/bin/myservice

That's the entire "become nobody" ceremony an init system bakes in.

Ambient capabilities, without the C. Say you want ping to run as an unprivileged UID but still hold CAP_NET_RAW. Setuid works but every child inherits root. setcap on the binary works but is fragile across bind-mounts and copies. Ambient caps let a privileged parent hand a capability across execve to an unprivileged child:

setpriv --reuid=1000 --regid=1000 --clear-groups \
        --inh-caps=+net_raw --ambient-caps=+net_raw \
        --bounding-set=-all,+net_raw \
        -- ping 1.1.1.1

Runs as UID 1000, can never gain another capability (bounding set trimmed to just NET_RAW), but pings just fine. This is how you'd write a modern network utility today.

Trim the bounding set for sandboxing. Even root can't reacquire a capability the bounding set has thrown away — the mask sticks across every execve in the tree:

setpriv --bounding-set=-sys_admin,-sys_module,-sys_ptrace \
        -- bash

Nothing in that shell — or its children, or its sudo'd children — can mount a filesystem, load a module, or attach a debugger. Container runtimes do exactly this internally.

Parent-death signal for cleanup. Wrappers that daemonize children often leak them when the parent dies. --pdeathsig tells the kernel to signal the child when the parent exits:

setpriv --pdeathsig=TERM -- long-running-worker

Why not sudo / runuser / su? Those exist to elevate a login session interactively. setpriv exists to configure a privilege set programmatically. Different jobs. If you're writing a shell script that needs to launch a helper with a specific UID and capability set, sudo -u drags in a whole session-management pipeline you don't want and can't easily disable. setpriv is what systemd's User=, AmbientCapabilities=, NoNewPrivileges=, and CapabilityBoundingSet= compile down to under the hood — exposed as a plain executable.

Read setpriv(1). It's three pages of what man capabilities and man prctl spend fifty pages describing.

Key Takeaway: When you need to launch a process with a precise UID, capability, and securebit configuration — not a login session — setpriv from util-linux does it in one execve, no PAM or sudoers baggage.

All newsletters