Setuid Program Example
Here’s an example showing how to set up a program that changes its effective user ID.
This is part of a game program called caber-toss
that manipulates file scores that should be writable only by the game program itself. The program assumes that its executable file will be installed with the setuid bit set and owned by the same user as the scores file. Typically, a system administrator will set up an account like games
for this purpose.
The executable file is given a mode 4755
, so that doing an ‘ls -l’ on it produces output like:
-rwsr-xr-x 1 games 184422 Jul 30 15:17 caber-toss
The setuid bit shows up in the file modes as the ‘s’.
The scores file is given mode 644
, and doing an ‘ls -l’ on it shows:
-rw-r--r-- 1 games 0 Jul 31 15:33 scores
Here are the parts of the program that show how to set up the changed user ID. This program is conditionalized so that it makes use of the file IDs feature if it is supported, and otherwise uses setreuid
to swap the effective and real user IDs.
#include <stdio.h> #include <sys/types.h> #include <unistd.h> #include <stdlib.h> /* Remember the effective and real UIDs. */ static uid_t euid, ruid; /* Restore the effective UID to its original value. */ void do_setuid (void) { int status; #ifdef _POSIX_SAVED_IDS status = seteuid (euid); #else status = setreuid (ruid, euid); #endif if (status < 0) { fprintf (stderr, "Couldn't set uid.\n"); exit (status); } }
/* Set the effective UID to the real UID. */ void undo_setuid (void) { int status; #ifdef _POSIX_SAVED_IDS status = seteuid (ruid); #else status = setreuid (euid, ruid); #endif if (status < 0) { fprintf (stderr, "Couldn't set uid.\n"); exit (status); } }
/* Main program. */ int main (void) { /* Remember the real and effective user IDs. */ ruid = getuid (); euid = geteuid (); undo_setuid (); /* Do the game and record the score. */ … }
Notice how the first thing the main
function does is to set the effective user ID back to the real user ID. This is so that any other file accesses that are performed while the user is playing the game use the real user ID for determining permissions. Only when the program needs to open the scores file does it switch back to the file user ID, like this:
/* Record the score. */ int record_score (int score) { FILE *stream; char *myname; /* Open the scores file. */ do_setuid (); stream = fopen (SCORES_FILE, "a"); undo_setuid ();
/* Write the score to the file. */ if (stream) { myname = cuserid (NULL); if (score < 0) fprintf (stream, "%10s: Couldn't lift the caber.\n", myname); else fprintf (stream, "%10s: %d feet.\n", myname, score); fclose (stream); return 0; } else return -1; }
Here’s an example function showing how to set up a program that changes its effective user ID and group ID.
#include <string.h> #include <stdlib.h> #include <unistd.h> #include <stdio.h> #include <sys/types.h> #include <pwd.h> #include <getopt.h> #include <grp.h> int switchUser(char *user) { struct passwd* pwd; uid_t uid = 0; gid_t gid = 0; int opt_idx = 0; /* If user not NULL, try to become switch. */ if (user != NULL && getuid() == 0 ) { printf("User: %s\n", user); pwd = getpwnam( user ); if(strcmp(user, "nobody")==0) { uid = gid = 0; } else { if ( pwd == (struct passwd*) 0 ) { printf("unknown user - '%s'\n", user); exit( 1 ); } uid = pwd->pw_uid; gid = pwd->pw_gid; } /* Set aux groups to null. */ if ( setgroups( 0, (const gid_t*) 0 ) < 0 ) { printf("setgroups - %m" ); exit( 1 ); } /* Set primary group. */ if ( setgid( gid ) < 0 ) { printf("setgid failed - %m"); exit( 1 ); } /* Try setting aux groups correctly - not critical if this fails. */ if ( initgroups( user, gid ) < 0 ) printf("initgroups - %m" ); /* Set uid. */ if ( setuid( uid ) < 0 ) { printf("setuid failed - %m"); exit( 1 ); } } printf("Hello World\n"); return 0; }