Chapter 60: Proc. Attributes: User IDs:UID, EUID & Group IDs:GID, EGID
Chapter Objectives
Upon completing this chapter, you will be able to:
- Understand the fundamental role of user and group IDs in the Linux security and permissions model.
- Differentiate between real, effective, and saved set user/group IDs and explain the purpose of each.
- Implement C programs that correctly retrieve and manage process credentials using standard POSIX system calls.
- Configure and analyze the behavior of setuid and setgid executables to manage privileged operations securely.
- Debug common permission-related issues in embedded applications arising from incorrect ID management.
- Apply best practices for handling privileged operations in a secure and controlled manner on an embedded Linux system.
Introduction
In the intricate world of an operating system, not all processes are created equal. The Linux kernel, standing as a vigilant gatekeeper, must meticulously manage access to system resources—from files and directories to hardware devices. The primary mechanism for this control is the concept of ownership and permissions. This chapter delves into the cornerstone of this mechanism: the user and group identifiers (UIDs and GIDs) associated with every running process. Understanding these attributes is not merely an academic exercise; it is fundamental to building secure, stable, and robust embedded systems.
Imagine an embedded device like a network-attached storage (NAS) system. It runs a web server to provide a management interface, a file-sharing service like Samba, and perhaps a media streaming application. The web server process should not have the ability to arbitrarily delete files managed by the Samba service. Likewise, the media streamer should not be able to reconfigure the network settings. The kernel enforces this separation of duties by examining the credentials of each process. These credentials—the real user ID (UID), effective user ID (EUID), real group ID (GID), and effective group ID (EGID)—determine what a process is and what it is allowed to do.
This chapter will guide you through the theory and practice of process credentials. We will explore why the distinction between real and effective IDs is one of the most elegant and critical aspects of the UNIX and Linux security model, enabling the principle of least privilege. You will learn how a process can temporarily gain elevated rights to perform a specific task and then drop those rights, minimizing the window of opportunity for potential exploits. Through practical C examples compiled and run on the Raspberry Pi 5, you will learn to programmatically inspect these IDs and create specialized programs that operate with elevated permissions in a controlled and secure manner.
Technical Background
At the heart of the Linux security model lies a simple yet powerful concept inherited from its UNIX predecessors: every process has an owner, and its access rights are primarily determined by that ownership. This identity is not a string of text like a username, but a number—a user ID (UID). Similarly, processes belong to a group, identified by a group ID (GID). These numeric identifiers are the currency of permissions within the kernel. When a process attempts to open a file, initialize a network socket, or access a GPIO pin, the kernel scrutinizes its UID and GID against the ownership and permission flags of the resource in question. This section dissects the different types of user and group IDs, explaining their distinct roles in managing process privileges.
Real vs. Effective IDs: The Principle of Least Privilege
When a user logs into a Linux system and starts a program, the resulting process inherits the user’s UID and GID. These initial identifiers are known as the real user ID (RUID) and real group ID (RGID). The real UID identifies the actual user who launched the process. For the most part, it remains constant throughout the process’s lifecycle and serves as an unchanging record of “who” is ultimately responsible for the process’s actions.
However, many essential system tasks require privileges beyond those of a normal user. Consider the passwd
utility, which allows users to change their own passwords. This operation requires modifying the /etc/shadow
file, a critical system file that stores hashed passwords and is, for obvious security reasons, readable only by the root user. If the passwd
program ran solely with the UID of the user executing it, it would be denied permission to write to /etc/shadow
. This presents a classic privilege dilemma.
To solve this, UNIX designers introduced the concept of the effective user ID (EUID) and effective group ID (EGID). The effective IDs are the ones the kernel actually checks when determining a process’s access rights to a resource. For most processes, the real and effective IDs are identical. However, Linux provides a mechanism to create programs that, when executed, run with an EUID different from the RUID of the user who launched them.
This mechanism is embodied by a special permission bit on an executable file known as the set-user-ID (setuid) bit. When a file with the setuid bit enabled is executed, the kernel performs a specific action: it sets the process’s effective user ID (EUID) to be the same as the UID of the user who owns the file, not the user who is running it. The real UID, however, remains that of the calling user.
graph TD A["Process calls open(/path/to/file)"] --> B{System Call Trap}; B --> C[Kernel's VFS receives request]; C --> D{"Get Process Credentials<br><b>EUID</b> & <b>EGID</b>"}; D --> E{"Get File Metadata (Inode)<br><b>Owner UID</b>, <b>Group GID</b>, & <b>Mode (rwx)</b>"}; E --> F{"Is Process EUID == 0 (root)?"}; F -- Yes --> G[Access Granted*<br><i>*Almost always</i>]; F -- No --> H{Is Process EUID == File's Owner UID?}; H -- Yes --> I{"Check <b>User</b> Permissions<br>e.g., r w -"}; I -- "Sufficient?" --> G; I -- "Insufficient" --> Z[Access Denied!]; H -- No --> J{Is Process EGID == File's Group GID?}; J -- Yes --> K{"Check <b>Group</b> Permissions<br>e.g., r - -"}; K -- "Sufficient?" --> G; K -- "Insufficient" --> Z; J -- No --> L{"Check <b>Other</b> Permissions<br>e.g., r - -"}; L -- "Sufficient?" --> G; L -- "Insufficient" --> Z; %% Styling classDef default fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937; classDef startNode fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff; classDef endNode fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff; classDef processNode fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; classDef systemNode fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff; classDef decisionNode fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff; classDef checkNode fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff; class A startNode; class G endNode; class Z checkNode; class B,C,D,E processNode; class F,H,J,I,K,L decisionNode;
Let’s return to the passwd
example. The executable file /usr/bin/passwd
is owned by the root user, and it has the setuid bit set. When a regular user, say student
(with UID 1001), runs passwd
, the kernel creates a new process. The real UID of this process is 1001 (student
). But because of the setuid bit on the executable, the kernel sets the effective UID to 0 (root
). Now, when the process attempts to modify /etc/shadow
, the kernel checks its EUID. Seeing that the EUID is 0, it grants the necessary permissions. The process can update the password file, but only in the highly controlled manner dictated by the passwd
program’s code. Once the passwd
process terminates, the temporary root privilege vanishes with it.
This elegant separation of real and effective identity is the cornerstone of the principle of least privilege in Linux. The process is granted just enough power, for just enough time, to perform its specific, privileged task.
graph TD subgraph "Setuid Execution (e.g., ./passwd)" direction TB G["User <i>student</i> (UID 1001) runs <i>passwd</i>"] --> H{Process Created}; H --> I{Kernel Checks File Permissions}; I --> J{"'passwd' is owned by 'root'<br><b>and has setuid bit set</b>"}; J --> K[Kernel Assigns Special Credentials]; K --> L["<b>RUID: 1001</b> (The user)<br><b>EUID: 0</b> (The file owner)"]; L --> M{Access /etc/shadow?}; M -- "EUID(0) == root" --> N[Access Granted!]; end subgraph "Normal Execution (e.g., ./ls)" direction TB A["User <i>student</i> (UID 1001) runs <i>ls</i>"] --> B{Process Created}; B --> C[Kernel Assigns Credentials]; C --> D[<b>RUID: 1001</b><br><b>EUID: 1001</b>]; D --> E{Access File?}; E -- "EUID(1001) vs. File Owner/Perms" --> F[Access Granted/Denied]; end %% Styling classDef default fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937; classDef startNode fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff; classDef processNode fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; classDef systemNode fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff; classDef decisionNode fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff; classDef successNode fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff; classDef checkNode fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff; class A,G startNode; class B,C,E,H,I,K,M processNode; class J,L systemNode; class D systemNode; class F,N successNode;
The Saved Set-User-ID: Retaining and Reclaiming Privilege
The distinction between real and effective IDs is powerful, but it introduces a new requirement: the ability for a privileged process to temporarily drop its elevated rights and later reclaim them. A setuid-root program does not need root privileges for its entire execution. For instance, it might need root access only at the very beginning to open a protected device file and at the very end to write a log file. In between, it might perform complex data processing or user interaction that is best done without elevated privileges, again adhering to the principle of least privilege.
If a process simply changes its effective UID from 0 (root) back to its real UID (e.g., 1001), it loses its root powers. But how can it get them back? A non-privileged user (anyone other than root) cannot arbitrarily change their EUID to 0. If they could, the entire security model would collapse.
This is where the third type of user ID comes into play: the saved set-user-ID (SUID). When a program with the setuid bit is executed, the kernel not only sets the EUID to the file owner’s UID but also copies this same value into the saved set-user-ID. The SUID acts as a “memory” of the privilege the process was granted at launch.
A process can use the seteuid()
or setreuid()
system calls to switch its effective UID back and forth between the values stored in its real UID and its saved set-UID. A setuid-root program running with RUID=1001, EUID=0, and SUID=0 can do the following:
- Perform privileged operation: Use its EUID of 0 to open a protected resource.
- Drop privilege: Call
seteuid(1001)
to change its EUID to its RUID. Now, EUID=1001, and the process operates as the regular user. It cannot perform further root-level actions. - Reclaim privilege: Call
seteuid(0)
to change its EUID back to its SUID. Now, EUID=0 again, and the process has regained its root powers for another specific task.
This ability to temporarily and reversibly de-escalate privileges is a critical security feature. It allows developers of setuid programs to write code where the vast majority of the logic runs without power, dramatically reducing the attack surface should a vulnerability be discovered in the non-privileged part of the code.
All three IDs—real, effective, and saved set—have corresponding group ID counterparts: real group ID (RGID), effective group ID (EGID), and saved set-group-ID (SGID). They function identically, but based on group ownership and the setgid permission bit on an executable file. A setgid program grants the process an effective GID corresponding to the group that owns the executable file, allowing access to resources based on group permissions.
graph TD A["Start: execve() on setuid-root program by user 'student' (UID 1001)"] --> B{Kernel Initializes Credentials}; B --> C["<b>Process State 1: Privileged</b><br>RUID: 1001<br>EUID: 0 (from file owner)<br>SUID: 0 (copied from EUID)"]; C --> D{"Privileged Operation<br><i>e.g., open('/dev/mem', O_RDWR)</i>"}; D -- "EUID is 0" --> E[Success!]; E --> F{"Time for non-privileged work.<br>Call <b>seteuid(1001)</b> to drop privilege."}; F --> G["<b>Process State 2: Demoted</b><br>RUID: 1001<br>EUID: 1001 (changed)<br>SUID: 0 (<b>unchanged</b>)"]; G --> H{"Attempt Privileged Operation Now?<br><i>e.g., fopen('/etc/shadow', 'r')</i>"}; H -- "EUID is 1001" --> I[Permission Denied!]; I --> J{"Need privileges again.<br>Call <b>seteuid(0)</b> to reclaim privilege."}; J -- "Kernel checks: Is 0 the RUID or SUID?" --> K{It matches the SUID!}; K --> L["<b>Process State 3: Reclaimed</b><br>RUID: 1001<br>EUID: 0 (restored)<br>SUID: 0 (unchanged)"]; L --> M{"Final Privileged Operation<br><i>e.g., write_log() to protected file</i>"}; M -- "EUID is 0" --> N[Success!]; %% Styling classDef default fill:#f8fafc,stroke:#64748b,stroke-width:1px,color:#1f2937; classDef startNode fill:#1e3a8a,stroke:#1e3a8a,stroke-width:2px,color:#ffffff; classDef endNode fill:#10b981,stroke:#10b981,stroke-width:2px,color:#ffffff; classDef processNode fill:#0d9488,stroke:#0d9488,stroke-width:1px,color:#ffffff; classDef systemNode fill:#8b5cf6,stroke:#8b5cf6,stroke-width:1px,color:#ffffff; classDef decisionNode fill:#f59e0b,stroke:#f59e0b,stroke-width:1px,color:#ffffff; classDef checkNode fill:#ef4444,stroke:#ef4444,stroke-width:1px,color:#ffffff; classDef warnNode fill:#eab308,stroke:#eab308,stroke-width:1px,color:#1f2937; class A startNode; class B,D,F,H,J,M processNode; class C,G,L systemNode; class E,K,N successNode; class I checkNode;
System Calls for Managing Process Credentials
Linux provides a suite of system calls, defined by the POSIX standard, for inspecting and manipulating these process credentials. These are the primary tools a C programmer will use to interact with the security model at a low level.
To simply retrieve the various IDs, the following calls are used:
uid_t getuid(void);
– Returns the real user ID of the calling process.uid_t geteuid(void);
– Returns the effective user ID of the calling process.gid_t getgid(void);
– Returns the real group ID of the calling process.gid_t getegid(void);
– Returns the effective group ID of the calling process.
These functions are straightforward and essential for a process to understand its own identity and current level of privilege. For example, a program might check geteuid()
at startup to confirm it has the root privileges it needs before proceeding.
Manipulating IDs is a more complex and security-sensitive operation. The primary system calls for this are:
int setuid(uid_t uid);
– This call attempts to set the real, effective, and saved set-user-IDs of the process touid
. If the process is not privileged (i.e., its effective UID is not 0), it can only set its IDs to its current real UID or saved set-UID. A privileged process (EUID=0) can set its IDs to anyuid
. This is a powerful and permanent change for the life of the process.int seteuid(uid_t euid);
– This is the preferred modern call for temporarily changing privileges. It only changes the effective user ID. A non-privileged process can only change its EUID to its current RUID or SUID. A privileged process can change its EUID to any arbitrary value, though it is almost always used to switch between the RUID and SUID.int setreuid(uid_t ruid, uid_t euid);
– A more flexible, but sometimes confusing, call that can set the real and effective UIDs. Passing -1 as a value for either argument indicates that the corresponding ID should not be changed. Its behavior regarding the saved set-UID has varied across systems, makingseteuid
a more portable and predictable choice for privilege management.
Analogous calls (setgid
, setegid
, setregid
) exist for managing group IDs. Understanding the subtle differences between these calls, especially the conditions under which they will succeed or fail, is crucial for writing secure, privileged programs. A common mistake is using setuid
when seteuid
is the correct tool for temporarily dropping privileges, leading to an irreversible loss of privilege.
Practical Examples
Theory provides the foundation, but true understanding comes from hands-on practice. In this section, we will use the Raspberry Pi 5 to explore process credentials in a practical setting. We will write, compile, and execute C programs that demonstrate the concepts of real, effective, and saved IDs.
Tip: Before you begin, ensure you have the
build-essential
package installed on your Raspberry Pi OS to get the GCC compiler and related tools:sudo apt update && sudo apt install build-essential
.
Example 1: Inspecting Your Own Process IDs
Our first program is a simple utility that reports its own process credentials. This helps establish a baseline for understanding what a normal, non-privileged process looks like to the kernel.
Code Snippet: id_inspector.c
Create a file named id_inspector.c
with the following content. The code uses the standard system calls to fetch and print the RUID, EUID, RGID, and EGID.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
/*
* id_inspector.c: A simple program to display the real and effective
* user and group IDs of the process.
*/
int main() {
uid_t ruid, euid;
gid_t rgid, egid;
// Get the User IDs
ruid = getuid();
euid = geteuid();
// Get the Group IDs
rgid = getgid();
egid = getegid();
printf("--- Process Credential Inspector ---\n");
printf("Real User ID (RUID) : %d\n", ruid);
printf("Effective User ID (EUID) : %d\n", euid);
printf("Real Group ID (RGID) : %d\n", rgid);
printf("Effective Group ID (EGID): %d\n", egid);
printf("------------------------------------\n");
// Check if the process is running with elevated privileges
if (euid == 0) {
printf("INFO: This process is running with root privileges (EUID=0).\n");
} else {
printf("INFO: This process is running as a non-privileged user.\n");
}
return 0;
}
Build and Execution Steps
- Compile the code: Open a terminal on your Raspberry Pi and compile the program using GCC. The
-o
flag specifies the name of the output executable.gcc id_inspector.c -o id_inspector
- Run as a normal user: Execute the compiled program directly. The default user on Raspberry Pi OS is
pi
, which typically has a UID of 1000../id_inspector
Expected Output (as pi
user)
You should see output similar to this. Note that the real and effective IDs are identical.
--- Process Credential Inspector ---
Real User ID (RUID) : 1000
Effective User ID (EUID) : 1000
Real Group ID (RGID) : 1000
Effective Group ID (EGID): 1000
------------------------------------
INFO: This process is running as a non-privileged user.
- Run with
sudo
: Now, execute the same program usingsudo
. Thesudo
command runs the program with root privileges.sudo ./id_inspector
Expected Output (with sudo
)
This time, the output will show that all IDs are 0, which is the identifier for the root
user.
--- Process Credential Inspector ---
Real User ID (RUID) : 0
Effective User ID (EUID) : 0
Real Group ID (RGID) : 0
Effective Group ID (EGID): 0
------------------------------------
INFO: This process is running with root privileges (EUID=0).
This simple experiment clearly demonstrates the difference in credentials between a process run by a regular user and one executed with superuser privileges.
Example 2: The Setuid Mechanism in Action
Now for the more interesting case: creating a program that temporarily elevates its own privilege using the setuid bit. Our program, priv_op
, will attempt to read the first line of the /etc/shadow
file—an action that should only be possible for root.
Code Snippet: priv_op.c
This program will first print its IDs, then attempt the privileged operation, and finally print its IDs again.
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
/*
* priv_op.c: Demonstrates the effect of the setuid bit.
* This program attempts to open and read from /etc/shadow,
* a file only accessible by root.
*/
void perform_privileged_read() {
FILE *fp;
char buffer[256];
printf("\nAttempting to read /etc/shadow...\n");
fp = fopen("/etc/shadow", "r");
if (fp == NULL) {
perror("ERROR: fopen failed");
return;
}
printf("SUCCESS: /etc/shadow opened.\n");
if (fgets(buffer, sizeof(buffer), fp) != NULL) {
printf("First line of /etc/shadow: %s\n", buffer);
}
fclose(fp);
}
void report_ids(const char* stage) {
printf("\n--- IDs at stage: %s ---\n", stage);
printf("RUID: %d, EUID: %d, RGID: %d, EGID: %d\n",
getuid(), geteuid(), getgid(), getegid());
printf("---------------------------------\n");
}
int main() {
report_ids("Process Start");
perform_privileged_read();
report_ids("After Operation");
return 0;
}
Build and Configuration Steps
1. Compile the program:
gcc priv_op.c -o priv_op
2. Run without setuid (as a normal user): First, let’s confirm it fails as expected.
./priv_op
The output will show that the fopen
call fails with a “Permission denied” error, because the process’s EUID is 1000.
--- IDs at stage: Process Start ---
RUID: 1000, EUID: 1000, RGID: 1000, EGID: 1000
---------------------------------
Attempting to read /etc/shadow...
ERROR: fopen failed: Permission denied
--- IDs at stage: After Operation ---
RUID: 1000, EUID: 1000, RGID: 1000, EGID: 1000
---------------------------------
3. Configure Permissions: This is the critical step. We need to change the ownership of the executable to root
and then set the setuid bit.Warning: Creating setuid-root programs carries inherent security risks. Always ensure the code is simple, secure, and does not provide an avenue for arbitrary command execution. This example is for educational purposes.
# Change the file owner to root. This requires sudo.
sudo chown root:root priv_op
# Set the setuid permission bit.
sudo chmod u+s priv_op
4. Verify Permissions: Use ls -l
to inspect the file’s permissions. The ‘s’ in the user execute permission field indicates the setuid bit is active.
ls -l priv_op
The output should look like this:
-rwsr-xr-x 1 root root 70976 Jul 28 20:59 priv_op
Flash, Boot, and Execute
Now, with the permissions correctly configured, execute the program again as the normal pi
user. Do not use sudo
.
./priv_op
Expected Output (with setuid)
This time, the program succeeds. Observe the IDs carefully. The Real UID (RUID) is still 1000 (the user who ran it), but the Effective UID (EUID) is 0 (the user who owns the file). The kernel used the EUID for the fopen
permission check, which is why it succeeded.
--- IDs at stage: Process Start ---
RUID: 1000, EUID: 0, RGID: 1000, EGID: 1000
---------------------------------
Attempting to read /etc/shadow...
SUCCESS: /etc/shadow opened.
First line of /etc/shadow: root:*:20221:0:99999:7:::
--- IDs at stage: After Operation ---
RUID: 1000, EUID: 0, RGID: 1000, EGID: 1000
---------------------------------
This example provides concrete proof of the setuid mechanism. The process inherited its real identity from the caller (pi
) but gained its effective power from the executable file’s owner (root
).
Example 3: Dropping and Reclaiming Privileges
Our final example demonstrates the use of the saved set-user-ID to manage privileges dynamically. The program will start with root privileges, drop them to perform a safe operation, and then reclaim them to perform a final privileged task.
Code Snippet: priv_manager.c
#define _GNU_SOURCE // For getresuid()
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>
#include <errno.h>
/*
* priv_manager.c: Demonstrates dropping and reclaiming privileges
* using seteuid() and the saved set-user-ID.
*/
void report_ids(const char* stage) {
uid_t ruid, euid, suid;
// getresuid is a Linux-specific extension to get all three IDs
if (getresuid(&ruid, &euid, &suid) == -1) {
perror("getresuid failed");
return;
}
printf("\n--- IDs at stage: %s ---\n", stage);
printf("RUID: %d, EUID: %d, SUID: %d\n", ruid, euid, suid);
printf("---------------------------------\n");
}
int main() {
uid_t ruid = getuid();
report_ids("Initial State (Privileged)");
printf(">> Dropping privileges by setting EUID to RUID...\n");
if (seteuid(ruid) == -1) {
perror("FATAL: Could not drop privileges");
exit(EXIT_FAILURE);
}
report_ids("After Dropping Privileges");
printf(">> Now running as non-privileged user. Cannot open /etc/shadow.\n");
FILE *fp = fopen("/etc/shadow", "r");
if (fp == NULL) {
perror("INFO: fopen failed as expected");
} else {
printf("WARN: Should not have been able to open /etc/shadow!\n");
fclose(fp);
}
printf("\n>> Reclaiming privileges by setting EUID back to 0 (from SUID)...\n");
if (seteuid(0) == -1) {
perror("FATAL: Could not reclaim privileges");
exit(EXIT_FAILURE);
}
report_ids("After Reclaiming Privileges");
printf(">> Privileges restored. Now we can open /etc/shadow again.\n");
fp = fopen("/etc/shadow", "r");
if (fp != NULL) {
printf("SUCCESS: Opened /etc/shadow after reclaiming privileges.\n");
fclose(fp);
} else {
perror("ERROR: Failed to open /etc/shadow after reclaiming privilege");
}
return 0;
}
Build and Configuration
The build and configuration steps are identical to Example 2.
1. Compile:
gcc priv_manager.c -o priv_manager
2. Set Ownership:
sudo chown root:root priv_manager
3. Set Permissions:
sudo chmod u+s priv_manager
Execution and Expected Output
Run the program as the pi
user:
./priv_manager
The output clearly shows the EUID changing, demonstrating the privilege management flow.
--- IDs at stage: Initial State (Privileged) ---
RUID: 1000, EUID: 0, SUID: 0
---------------------------------
>> Dropping privileges by setting EUID to RUID...
--- IDs at stage: After Dropping Privileges ---
RUID: 1000, EUID: 1000, SUID: 0
---------------------------------
>> Now running as non-privileged user. Cannot open /etc/shadow.
INFO: fopen failed as expected: Permission denied
>> Reclaiming privileges by setting EUID back to 0 (from SUID)...
--- IDs at stage: After Reclaiming Privileges ---
RUID: 1000, EUID: 0, SUID: 0
---------------------------------
>> Privileges restored. Now we can open /etc/shadow again.
SUCCESS: Opened /etc/shadow after reclaiming privileges.
Notice the Saved Set-UID (SUID) remained 0 throughout. This “memory” of the initial privilege is what allowed the process to use seteuid(0)
and successfully restore its effective UID to root, even when it was running with an EUID of 1000.
Common Mistakes & Troubleshooting
Managing process credentials can be tricky, and errors often lead to security vulnerabilities or non-functional programs. Here are some common pitfalls and how to avoid them.
Exercises
These exercises will reinforce your understanding of process credentials. They are designed to be completed on your Raspberry Pi 5.
- User and Group Name Resolver:
- Objective: Modify the
id_inspector.c
program to print the names of the user and group in addition to their numeric IDs. - Guidance: You will need to research the
getpwuid()
andgetgrgid()
library functions. These functions take a UID or GID and return a pointer to astruct passwd
orstruct group
, respectively, which contain the username and group name as strings. Remember to include the necessary headers (pwd.h
andgrp.h
). - Verification: Run the program normally and with
sudo
. The output should correctly displaypi
for UID 1000 androot
for UID 0.
- Objective: Modify the
- Setgid Demonstration:
- Objective: Create a program that demonstrates the setgid mechanism.
- Guidance:
- Create a new group on your system (e.g.,
sudo addgroup dataloggers
). - Create a file in
/tmp/
that is owned byroot
and writable only by thedataloggers
group (e.g.,sudo touch /tmp/sensor.log; sudo chown root:dataloggers /tmp/sensor.log; sudo chmod 664 /tmp/sensor.log
). - Write a C program that attempts to open this file for writing.
- Compile it, change its group ownership to
dataloggers
(sudo chown root:dataloggers myprogram
), and set the setgid bit (sudo chmod g+s myprogram
).
- Create a new group on your system (e.g.,
- Verification: When run by the
pi
user (who is not in thedataloggers
group), the program should successfully open the file for writing because its EGID will be that of thedataloggers
group. Use theid_inspector
logic to print the IDs and confirm the EGID changes.
- Privilege Sandwich Implementation:
- Objective: Refactor the
priv_op.c
program to correctly implement the privilege sandwich pattern. - Guidance: The program should start, report its IDs, immediately drop privileges using
seteuid()
, perform some non-privileged work (e.g., print a message to the console), reclaim privileges usingseteuid()
, perform the privileged read of/etc/shadow
, and then exit. - Verification: The program’s output should clearly show the EUID changing from 0 to 1000, and then back to 0. The privileged read should only succeed after privileges have been reclaimed.
- Objective: Refactor the
- Checking for Privilege:
- Objective: Write a utility that can be used in a shell script to check if it’s running as root.
- Guidance: Write a C program that simply checks if
geteuid()
returns 0. If it does, the program should exit with a status code of 0 (success). If it does not, it should print a message tostderr
and exit with a status code of 1 (failure). - Verification: You can test this in a shell script:
#!/bin/bash if ./check_root; then echo "Script is running as root." else echo "Script is NOT running as root. Aborting." exit 1 fi
Run this script normally and withsudo
to see the different outcomes.
Summary
- Process Credentials: Every Linux process has a set of user and group IDs that define its ownership and permissions.
- Real vs. Effective IDs: The Real UID (RUID) identifies the user who launched the process. The Effective UID (EUID) is what the kernel checks for permission decisions. For most processes, they are the same.
- Setuid and Setgid Bits: These special file permissions allow a process to gain an effective ID from the file’s owner/group, enabling controlled privilege escalation.
- Saved Set-ID: The Saved Set-UID (SUID) stores the initial privileged EUID, allowing a process to temporarily drop and later reclaim its elevated rights using the
seteuid()
system call. - Principle of Least Privilege: By managing RUID, EUID, and SUID, developers can write secure programs that only use elevated privileges for the brief moments they are absolutely necessary, minimizing the system’s attack surface.
- System Calls: Functions like
getuid()
,geteuid()
,seteuid()
, andsetreuid()
are the standard C library interfaces for inspecting and manipulating process credentials.
Further Reading
- The Linux Programming Interface by Michael Kerrisk. Chapters 8 and 9 provide an exhaustive and authoritative discussion of process credentials and setuid/setgid operations.
- Advanced Programming in the UNIX Environment by W. Richard Stevens and Stephen A. Rago. A classic text whose chapters on process control and security are still highly relevant.
- Official Linux
man
pages: The manual pages forcredentials(7)
,setuid(2)
,seteuid(2)
, andgetuid(2)
are the definitive source for system-specific details. Access them viaman 7 credentials
. - POSIX.1-2017 Standard: The official standard defining how these system calls should behave. The specifications for
getuid
,setuid
, etc., can be found on the Open Group’s website. - Smashing The Stack For Fun And Profit by Aleph One. While older, this seminal paper provides crucial insight into the types of vulnerabilities that can arise from insecurely written setuid programs, reinforcing the need for careful design.
- Buildroot User Manual: For embedded developers, understanding how Buildroot handles file permissions and ownership during rootfs creation is essential for deploying setuid applications correctly.
- Yocto Project and Embedded Linux Development Courseware: The Yocto Project documentation contains valuable information on creating recipes that correctly handle permissions for custom executables in an embedded context.