analyst@unixshadow:~$ sudo -l # what happens between typing sudo and getting a root shell
How sudo Really Works
setuid, PAM, and the sudoers File
The setuid post explained the mechanism that makes privilege escalation possible: a binary with the setuid bit runs with its owner’s privileges, not the caller’s. sudo is the binary that mechanism was built for. This post traces exactly what happens between typing sudo and getting a root shell — the setuid bit, the PAM authentication, the sudoers parsing, and why sudo -l matters. It is the sequel the setuid post promised, and it is the post to read before editing /etc/sudoers for the first time.
# step 1: the setuid bit — the binary runs as root before it does anything else
sudo is a setuid-root binary.
This is why sudo’s own permissions matter. If
sudo does not stay root. The first thing it does after starting is drop back to the calling user’s real UID for most of its work — reading configuration, checking the timestamp — and only re-elevates to root when it is about to execute the requested command. A setuid binary that ran as root for its entire lifetime would be a much larger target; sudo minimises the window.
ls -l /usr/bin/sudo shows ---s--x--x — the s in the owner’s execute position is the setuid bit, and the owner is root. When an ordinary user runs sudo, the kernel starts the process with the effective UID of root, not the user’s UID. That is the whole trick, and it is the whole risk: everything sudo does from this point on, it does as root, and the only thing standing between the user and a root shell is sudo’s own code deciding whether to allow it.This is why sudo’s own permissions matter. If
/usr/bin/sudo were writable by anyone but root, anyone could replace it with a binary that does not check anything — and it would still run as root, because the setuid bit does not care what the binary does, only who owns it. The setuid post’s rule applies here first: the binary’s ownership and permissions are the security boundary, not the code inside it.sudo does not stay root. The first thing it does after starting is drop back to the calling user’s real UID for most of its work — reading configuration, checking the timestamp — and only re-elevates to root when it is about to execute the requested command. A setuid binary that ran as root for its entire lifetime would be a much larger target; sudo minimises the window.
# step 2: who are you, and are you allowed — sudoers first, then the password
sudo reads /etc/sudoers (and the files in /etc/sudoers.d/) and asks one question: is this user allowed to run this command, as this target user, on this host? The rule that matches is the answer, and the file is read top to bottom — the LAST matching rule wins, which is the single most common sudoers mistake. A broad allow early in the file does not protect you if a narrower deny, or a broader allow, comes later.
The rule format is the whole model:
Then, if the rule requires it, sudo asks for a password — and it asks for the CALLING user’s password, not root’s. That is the design: the password proves the person at the keyboard is the account holder, not that they know root’s secret. A rule with
The password check itself is PAM. sudo hands the authentication to the PAM stack —
visudo exists for exactly this reason: it locks the file, checks the syntax before saving, and refuses to save a broken sudoers file that would lock everyone out of root.The rule format is the whole model:
hafeez ALL=(ALL:ALL) ALL — user hafeez, on all hosts, may run any command as any user and any group.deploy web01=(www-data) /usr/bin/systemctl restart nginx — user deploy, only on host web01, may run one command, as www-data, and nothing else.Then, if the rule requires it, sudo asks for a password — and it asks for the CALLING user’s password, not root’s. That is the design: the password proves the person at the keyboard is the account holder, not that they know root’s secret. A rule with
NOPASSWD: skips this step entirely, which is convenient for automation and dangerous for anything a human types at — a stolen session with a NOPASSWD rule is a root shell with no further check.The password check itself is PAM. sudo hands the authentication to the PAM stack —
/etc/pam.d/sudo — which is the same mechanism that checks passwords at login. That is why a locked account, an expired password, or a failed PAM module can block sudo even when the sudoers rule is correct: sudoers says you are allowed, PAM says you are not who you claim to be, and PAM wins.# step 3: the timestamp — why sudo only asks sometimes
After a successful authentication, sudo records a timestamp — by default in
The timestamp is the reason
The timestamp file itself is protected: it is owned by root, in a root-owned directory, and sudo validates it before trusting it. Tampering with the timestamp does not grant access — it invalidates it.
/run/sudo/ts/ (older systems used /var/db/sudo or /var/run/sudo). For the next 15 minutes (the timestamp_timeout default), sudo will not ask for the password again. The timestamp is per-user and per-terminal by default (tty_tickets), so a sudo in one terminal does not authorise a sudo in another — a detail that matters on a shared screen or a multiplexed session.The timestamp is the reason
sudo -k exists. It invalidates the timestamp immediately, so the next sudo asks for the password again. Run it before walking away from a terminal, and run it in any script that ends a privileged session. The 15-minute window is a convenience, and like every convenience in sudo, it is a window in which a stolen terminal is a root shell.The timestamp file itself is protected: it is owned by root, in a root-owned directory, and sudo validates it before trusting it. Tampering with the timestamp does not grant access — it invalidates it.
# step 4: the command runs — and the audit trail is the point
Once the checks pass, sudo re-elevates to root, sets up the environment, and executes the command. The environment is the part people underestimate: sudo strips most environment variables by default (
Then it logs. Every sudo invocation — allowed or denied — goes to the system log (syslog, and on systemd systems the journal:
The command runs as the target user — root by default, or whoever the rule names — and when it exits, sudo exits with the same status. There is no persistent privileged process left behind. sudo is a gate, not a session: it opens, the command runs, it closes.
env_reset), because an environment variable like LD_PRELOAD handed to a root process is a root shell by another name. Variables that survive are the ones in env_keep, and the list is deliberately short.Then it logs. Every sudo invocation — allowed or denied — goes to the system log (syslog, and on systemd systems the journal:
journalctl -t sudo or journalctl _COMM=sudo). A denied sudo is logged with the user, the terminal, the command, and the reason. That log is the audit trail, and it is the reason sudo exists instead of sharing the root password: with a shared root password, nobody knows who did what; with sudo, every privileged command has a name on it.The command runs as the target user — root by default, or whoever the rule names — and when it exits, sudo exits with the same status. There is no persistent privileged process left behind. sudo is a gate, not a session: it opens, the command runs, it closes.
That is the whole design in one sentence: a setuid binary that checks a policy file, authenticates through PAM, caches the result for 15 minutes, runs one command as root, and writes down who asked for it.
# sudo -l: the command that matters most — read your own privileges before you need them
sudo -l lists exactly what the current user is allowed to run, and as whom. It is the single most useful sudo command for auditing a box, because it answers the question “what can this account do?” without guessing from the sudoers file — and it applies the same last-match-wins logic sudo itself uses, so what it shows is what sudo will enforce.Run it on every box you inherit. A user with
(ALL) NOPASSWD: ALL is root with a password prompt skipped. A user with a narrow rule — one command, one target user — is a service account with a job, not a privilege. The difference between those two lines is the difference between a box with an audit trail and a box with a root password by another name. sudo -l is how you tell which one you have, and it is the first command in any sudo audit because the sudoers file is long, the includes in /etc/sudoers.d/ are easy to miss, and the last matching rule is the one that counts.And the rule that follows from all of it: give a user the narrowest rule that lets them do their job, never
ALL when one command will do, never NOPASSWD when a password prompt is acceptable, and check with sudo -l after every change — because the sudoers file is the policy, and the policy is only as good as its last matching line.# quick reference — the whole post in six lines
setuid: /usr/bin/sudo runs as root from the moment it starts; its ownership and permissions are the boundary.
sudoers: last matching rule wins; edit only with visudo, which refuses to save a broken file.
Password: the calling user’s password, checked through PAM — a locked account blocks sudo even with a correct rule.
Timestamp: 15 minutes by default, per terminal; sudo -k invalidates it immediately.
Execution: environment stripped, command runs as the target user, every invocation logged — allowed or denied.
Audit: sudo -l shows exactly what an account can do; run it on every box you inherit, and after every sudoers change.
sudoers: last matching rule wins; edit only with visudo, which refuses to save a broken file.
Password: the calling user’s password, checked through PAM — a locked account blocks sudo even with a correct rule.
Timestamp: 15 minutes by default, per terminal; sudo -k invalidates it immediately.
Execution: environment stripped, command runs as the target user, every invocation logged — allowed or denied.
Audit: sudo -l shows exactly what an account can do; run it on every box you inherit, and after every sudoers change.
Series: Under the HoodStatus: Published 9 Nov 2026
