What is a Privileged Program?
In Linux and UNIX systems, most programs run with the same permissions as the user who launched them. A privileged program is special โ it can access system resources, files, and devices that are normally restricted to ordinary users. Think of these programs as having a “master key” to the system. With this power comes great responsibility: a bug in a privileged program can open the entire system to attack.
This chapter teaches you how to write such programs safely โ minimizing the chance of being exploited, and minimizing damage if an exploit does happen.
A program can run with elevated privileges in exactly two ways:
sshd, cron use this method.passwd, sudo.How Set-User-ID Works โ Step by Step
Normally when you run a program, your process gets your user ID (say, UID=1000). But if a file has the set-user-ID (SUID) bit set, and the file is owned by root, then when you run it, your process’s effective user ID becomes 0 (root) โ even though your real user ID is still 1000.
| Scenario | Real UID | Effective UID | Saved Set-UID |
|---|---|---|---|
| Normal program run by user 1000 | 1000 | 1000 | 1000 |
| Set-UID-root program run by user 1000 | 1000 | 0 (root) | 0 (root) |
| Set-UID program (owner=200) run by user 1000 | 1000 | 200 | 200 |
The Three User IDs You Must Know
Every Linux process carries three user IDs. Understanding them is fundamental to secure programming:
Who you actually are. Set at login. Doesn’t change when you run an SUID program.
What privilege checks use. This changes when you run an SUID program. Kernel uses this for permission decisions.
A saved copy of the EUID before you drop privilege. Allows the program to temporarily drop and then re-raise privilege.
The very best piece of advice from security experts is: don’t write set-UID programs unless you absolutely have to. Every line of code in a privileged program is a potential attack vector.
If the task can be done by a daemon running as a specific non-root user, or by adjusting file permissions, prefer that approach. Privilege is a last resort, not a convenience.
Isolate Privilege into a Separate Program
When some privilege is unavoidable, a powerful pattern is to isolate the privileged operation into a tiny helper program that does one thing and exits. The main program (unprivileged) forks and execs this helper only when needed. This limits the attack surface enormously.
Example: The pt_chown program used internally by the GNU C library is exactly such a helper โ it performs one privileged operation (changing ownership of a pseudo-terminal) and nothing else.
Use a Non-Root Identity When Possible
Sometimes full root privilege isn’t needed. If you just need to allow users to write to a protected file, consider this safer design pattern:
Before you can manage privilege, you need to see the current UID state of your process. This program prints all three user IDs:
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
int main(void)
{
uid_t ruid, euid, suid;
/* getresuid() โ Linux-specific, gets all three user IDs at once */
if (getresuid(&ruid, &euid, &suid) == -1) {
perror("getresuid");
return 1;
}
printf("Real UID (RUID): %d\n", (int)ruid);
printf("Effective UID (EUID): %d\n", (int)euid);
printf("Saved set-UID (SUID): %d\n", (int)suid);
if (euid == 0)
printf("Status: Running with ROOT privileges\n");
else if (euid != ruid)
printf("Status: Running with ELEVATED privileges (UID %d)\n", (int)euid);
else
printf("Status: Running with normal user privileges\n");
return 0;
}
How to compile and test:
# Compile
gcc -o show_ids show_ids.c
# Run as normal user โ all three IDs will be identical
./show_ids
# Make it set-UID-root to see the difference
sudo chown root show_ids
sudo chmod u+s show_ids
./show_ids
# Now EUID=0, RUID=your-uid, SUID=0
A program can detect whether it is running with elevated privilege by comparing the real and effective UIDs. This is a common check at program startup:
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
/*
* safe_check_privilege():
* Returns 1 if running with privilege (EUID != RUID or EUID == 0)
* Returns 0 otherwise
*/
int safe_check_privilege(void)
{
return (geteuid() != getuid()) || (geteuid() == 0);
}
int main(void)
{
printf("=== Privilege Diagnostic ===\n");
printf("Real UID : %d\n", (int)getuid());
printf("Effective UID: %d\n", (int)geteuid());
printf("Real GID : %d\n", (int)getgid());
printf("Effective GID: %d\n", (int)getegid());
if (safe_check_privilege()) {
printf("\n[!] WARNING: Program running with elevated privileges.\n");
printf(" Ensure privilege is dropped before untrusted operations.\n");
} else {
printf("\n[OK] Program running with normal user privileges.\n");
}
return 0;
}
This kind of check at startup is good practice โ it helps the program know its privilege state and act accordingly.
๐ Key Terms for This Section
sshd, cron) or has the set-UID/GID permission bit set (e.g., passwd, sudo).
Effective UID: What the kernel checks for permission decisions. Changes when running an SUID program.
Saved Set-UID: A copy of the EUID saved on exec. Allows temporarily dropping and re-raising privilege.
getresuid(uid_t *ruid, uid_t *euid, uid_t *suid) โ retrieves Real UID, Effective UID, and Saved Set-UID in one call. It’s Linux-specific but preferred because it gives a complete, unambiguous picture of privilege state.
