Safe Penetration Testing Labs · advanced · ~25 min

Auditing setuid programs

Run a defensive checklist over any setuid binary.

Overview

A setuid program runs with the privileges of the file's owner, not the user who launched it. If the owner is root, even a tiny bug can hand an attacker full control of the machine.

Auditing a setuid binary is a checklist. You walk it item by item. Anything that is not handled correctly counts as a finding.

Why it matters

During an authorised security assessment, you will often find setuid binaries on a target system.

The process is direct:

  • Read the program's source code, or disassemble it if no source is available.
  • Run the checklist against it.
  • Treat every missed item as a possible privilege-escalation path.

Each gap you find is something an attacker could use to gain higher privileges.

Core concepts

The 5-item checklist

1. Is the environment scrubbed?

The environment is the set of variables a process inherits (for example $PATH). A setuid binary that trusts variables like $PATH, $LD_PRELOAD, $LD_LIBRARY_PATH, $IFS, or $ENV can be compromised. An attacker controls these values before launching the program.

Always call clearenv first, then set only known-safe values.

2. Are file descriptors 0, 1, and 2 open?

These are standard input, output, and error. They should always be open. If one is closed, the next open call reuses that low number. A later printf (which writes to descriptor 1) could then write into one of your own data files instead of the screen.

3. Does the code use getuid() vs geteuid() correctly?

Both matter:

  • geteuid returns the effective user id — the elevated identity the program runs as.
  • getuid returns the real user id — the caller who launched it.

Making an authorisation decision against the wrong one is a classic security bug (CVE).

4. Are there any file races?

This follows the TOCTOU (time-of-check to time-of-use) and symlink lessons. Every path operation must use either openat with O_NOFOLLOW, or realpath followed by a prefix check.

5. Does it run external commands?

system(3) and popen(3) pass the current environment to the child process. If you must spawn a program, use execve with a sanitised envp.

The pentester mindset

Walk the list explicitly. Each "yes, this is a problem" is a finding worth its own separate write-up.

Syntax notes

uid_t getuid(void);          /* real uid — the caller */
uid_t geteuid(void);         /* effective uid — privileges in force */
int   setresuid(uid_t r, uid_t e, uid_t s);   /* drop privileges for good */
int   clearenv(void);        /* remove all environment variables */

Lesson

A setuid binary runs with the file owner's privileges, not the caller's.

When the owner is root, these binaries become small doorways into root access. Auditing one is a focused checklist:

  • PATH inheritance
  • environment scrubbing
  • file-descriptor leaks
  • file-race patterns
  • the getuid() / geteuid() distinction

Code examples

/* Defensive setuid program start-up: */
if (clearenv() != 0) exit(1);                /* strip ALL env first */
setenv("PATH", "/usr/bin:/bin", 1);          /* known-safe PATH */
if (setresuid(geteuid(), geteuid(), geteuid()) < 0) exit(1);

Line by line

/* Drop privs entirely the moment the elevated work is done */
uid_t real = getuid();
if (setresuid(real, real, real) < 0) {
    perror("setresuid"); exit(1);
}
/* Now we run as the original caller — much less attack surface */

Common mistakes

  • Trusting argv[0] for the program name. An attacker can set it to anything.

Debugging tips

Run ls -l /usr/bin/sudo. You will see -rwsr-xr-x. The s in that permission string is the setuid bit.

To list every setuid file on a system:

find / -perm -4000 -type f 2>/dev/null

Memory safety

The checklist is not the whole story. The usual memory-safety rules still apply: use bounded copies, validate all input, and so on.

Real-world uses

Common setuid programs include sudo, su, ping (historically), mount (historically), and passwd.

Each one ships with this kind of audit already built in. Review their source to see the patterns in real code.

Practice tasks

  1. Walk the 5-item checklist over the source of /usr/bin/sudo (it is open source).
  2. Build a small setuid program of your own, then run the checklist against your code.
  3. Demonstrate why clearenv matters by exploiting a program through LD_PRELOAD.

Summary

  • A setuid program runs with the file owner's privileges, so a small bug can mean full compromise.
  • Audit with the 5-item checklist: environment, file descriptors, uids, file races, and child processes.
  • Use geteuid for the elevated id and getuid for the caller; never confuse them in auth decisions.
  • Every missed checklist item is a privilege-escalation candidate worth a separate write-up.

Practice with these exercises