/**
@file suid0tmp.c
@author Mitch Richling <http://www.mitchr.me/>
@Copyright Copyright 1994 by Mitch Richling. All rights reserved.
@brief SUID0 template in C@EOL
@Keywords suid root template
@Std ISOC POSIX
This is an example program intended to illustrate how to
develop an SUID0 program. This code demonstrates a
reasonable overall structure for an SUID0 program.
Several things are demonstrated:
* how to query uid/euid/gid/egid,
* how to log to syslog
* How to use asprintf to avoid the buffer overflows,
that haunt sprintf
* How to sanitize the user environment
* How to make sure open files inherited
from the parent process are safe.
* How to completely change user identity to increase
privileges.
* How to permanently surrender SUID privileges.
NOTE: In general, the "system" function should not be
used in an SUID0 program, but it is used here for
illustration.
On a BSD system, the following sequence of commands
will create a working program:
@Build cc suid0tmp.c -o suid0tmp; chown 0.0 suid0tmp;chmod a+rxs suid0tmp
@Tested
- BROKEN: Solaris 2.8 (No paths.h)
- MacOS X.2
- Linux (RH 7.3)
*/
#include <errno.h> /* error stf POSIX */
#include <paths.h> /* UNIX Paths ???? */
#include <pwd.h> /* UNIX passwd POSIX */
#include <stdio.h> /* I/O lib ISOC */
#include <stdlib.h> /* Standard Lib ISOC */
#include <sys/stat.h> /* UNIX stat POSIX */
#include <sys/types.h> /* UNIX types POSIX */
#include <syslog.h> /* UNIX syslog UNIX */
#include <unistd.h> /* UNIX std stf POSIX */
#include <utime.h> /* utime POSIX */
#include <string.h> /* Strings ISOC */
/** define the uid we will set the binary to. */
#define TARGETEUID 0
/** define the gid we will set the binary to. */
#define TARGETEGID 0
#define PROGNAME "suid0tmp"
#define WHOAMIBIN "/usr/bin/whoami"
/* #define WHOAMIBIN "/usr/ucb/whoami" */
int main(int argc, char *argv[]);
int secureEnv(char **keepEnv, char **setEnv, char **defEnv);
void secureEnvOrExit(char **keepEnv, char **setEnv, char **defEnv);
void secureFDsOrExit();
int secureFDs();
extern char **environ;
int main(int argc, char *argv[]) {
uid_t theUid;
uid_t theEuid;
gid_t theGid;
gid_t theEgid;
char *strBuf;
/* Close all FDs over 2, and make sure all FDs under 3 are
open. Exit if we can't fix the FDs.*/
secureFDsOrExit();
/* Discover who the user is, and store the info. At this point the
euid/egid should be set by the SUID/SGID bits of the binary. The
uid/gid should be the uid/gid of the parent process -- the real
user. */
theUid = getuid();
theEuid = geteuid();
theGid = getgid();
theEgid = getegid();
/* Log the activity. Note that we do not use sprintf as it is not
safe. Note also that we don't use snprintf as it is not portable
(it exists on many platforms including BSD and Linux). asprintf
is part of ANSI/ISO C 89. */
if(asprintf(&strBuf, "%s(%s): run by uid=%ld", PROGNAME, argv[0], (long)theUid) <= 0) {
printf("ERROR: asprinf() failure..\n");
exit(1);
} /* end if */
syslog(LOG_NOTICE | LOG_AUTH, strBuf);
/* If we didn't actually change user it could be that the filesystem
doesn't support SUID bits, or that the bits were not set on the
binary. Whatever the reason, we exit now. */
if(theEuid != TARGETEUID) {
printf("ERROR: SUID not granted.\n");
exit(1);
} /* end if */
/* If we didn't actually change group. Could be that the filesystem
doesn't support SUID bits, or that the bits were not set on the
binary. */
if(theEuid != TARGETEGID) {
printf("ERROR: SGID not granted.\n");
exit(1);
} /* end if */
/* Here we do whatever we want to with the current IDs */
printf("1 uid: %ld euid: %ld gid: %ld egid: %ld\n",
(long)getuid(), (long)geteuid(),
(long)getgid(), (long)getegid());
system(WHOAMIBIN);
/* Now we make sure we really look like the target by setting the
uid/gid as well. We can really only do this if the target UID is
0(root). */
setuid(TARGETEUID);
setgid(TARGETEGID);
printf("2 uid: %ld euid: %ld gid: %ld egid: %ld\n",
(long)getuid(), (long)geteuid(),
(long)getgid(), (long)getegid());
system(WHOAMIBIN);
/* Now we become the user -- or at least the uid/gid of the parent
process. Note we set the gid/egid first. If we set the uid/euid
first, we would not have privilege to change the gid/egid Again,
note that if the TARGET is not 0(root) we can't do this part. */
setgid(theGid);
setegid(theGid);
setuid(theUid);
seteuid(theUid);
printf("3 uid: %ld euid: %ld gid: %ld egid: %ld\n",
(long)getuid(), (long)geteuid(),
(long)getgid(), (long)getegid());
system(WHOAMIBIN);
/* Finish up */
return (0);
} /* end func main */
/* ******************************************************************************** */
void secureFDsOrExit() {
int secureFDreturn;
/* Close all FDs over 2, and make sure all FDs under 3 are open. */
secureFDreturn = secureFDs();
if(secureFDreturn == 1) {
printf("ERROR: Could not determine file descriptor table size.\n");
exit(1);
} else if( (secureFDreturn >= 2) && (secureFDreturn <= 4) ) {
printf("ERROR: Unknown stat error (FD: %d).\n", secureFDreturn-2);
exit(1);
} else if( (secureFDreturn >= 5) && (secureFDreturn <= 7) ) {
printf("ERROR: Could not reopen FD %d.\n", secureFDreturn-5);
exit(1);
} else if( (secureFDreturn >= 8) && (secureFDreturn <= 10) ) {
printf("ERROR: Wrong FD result from reopen of %d\n", secureFDreturn-8);
exit(1);
} /* end if/else */
} /* end func secureFDsOrExit */
/* ********************************************************************************
Closes all file descriptors greater than 2. Makes sure that stdin,
stdout, and stderr are open. If they are not, then it opens them.
Returns: -1 Everything worked (reopens required)
0 Everything worked
1 Error using getdtablesize
2 Unknown stat error (wasn't EBADF) stdin
3 Unknown stat error (wasn't EBADF) stdout
4 Unknown stat error (wasn't EBADF) stderr
5 If the reopen fails stdin
6 If the reopen fails stdout
7 If the reopen fails stderr
8 Wrong FD result from reopen stdin
9 Wrong FD result from reopen stdout
10 Wrong FD result from reopen stderr
*/
int secureFDs() {
int daReturn = 0;
int numFDs;
struct stat s;
FILE *f;
int fd;
if( (numFDs = getdtablesize()) < 0) {
return 1;
} /* end if */
/* Close all file descriptors except stdin, stdout, and stderr (0, 1, and 2) */
for(fd=3; fd<numFDs; fd++)
close(fd);
/* Make sure that FD 0, 1, and 2 are open. If they are not, then
open them and make sure stdin, stdout, and stderr are associated
with FD 0, 1, and 2. */
for(fd=0; fd<3; fd++) {
if(fstat(fd, &s) < 0) { /* It has a problem, or it is closed. */
daReturn = -1;
if(errno != EBADF) { /* EBADF == closed, most of the time. */
return 2+fd;
} /* end if */
/* fd is closed if we get here. */
f = NULL;
if(fd == 0) {
f = freopen(_PATH_DEVNULL, "rb", stdin);
} else if(fd == 1) {
f = freopen(_PATH_DEVNULL, "wb", stdout);
} else if(fd == 2) {
f = freopen(_PATH_DEVNULL, "wb", stderr);
} /* end if/else */
if(f == NULL) { /* The freopen failed. */
return 5+fd;
} /* end if */
if(fd != fileno(f)) { /* Somehow didn't get the right FD! */
return 8+fd;
} /* end if */
} /* end if */
} /* end for */
return daReturn;
} /* end secureFDs */
/* ******************************************************************************** */
/* This function calls secureEnv() and exits if anything goes wrong.
The arguments are the same as what secureEnv() requires, and they
are directly passed to secureEnv(). */
void secureEnvOrExit(char **keepEnv, char **setEnv, char **defEnv) {
int secureEnvReturn;
secureEnvReturn = secureEnv(keepEnv, setEnv, defEnv);
if (secureEnvReturn == 1) {
printf("ERROR: Could not zap environment variable.\n");
} else if(secureEnvReturn == 2) {
printf("ERROR: Insanely long environment variable.\n");
} else if(secureEnvReturn == 3) {
printf("ERROR: Could not set environment variable.\n");
} else if(secureEnvReturn == 4) {
printf("ERROR: Call to getpwuid failed.\n");
} else if(secureEnvReturn == 6) {
printf("ERROR: Could not set PATH to default value.\n");
} else if(secureEnvReturn == 7) {
printf("ERROR: Could not set TMPDIR to default value.\n");
} else if(secureEnvReturn == 8) {
printf("ERROR: Could not set HOME to default value.\n");
} else if(secureEnvReturn == 9) {
printf("ERROR: Could not set LOGNAME to default value.\n");
} else if(secureEnvReturn == 10) {
printf("ERROR: Could not set SHELL to default value.\n");
} /* end if/else */
if(secureEnvReturn > 0)
exit(1);
} /* end secureEnvReturn */
/* ******************************************************************************** */
/* Defaults: keepEnv==NULL => keep nothing
setEnv==NULL => set nothing
defEnv==NULL => set PATH, SHELL, LOGNAME, and HOME
Returns: 0 => Everything worked
1 => Malformed element of envdir (no '=' char)
2 => An environment variable name was very long (>512b)
3 => Could not set environment variable (from setEnv)
4 => Could not get passwd info (for defEnv)
5 => Not used
6 => Could not set PATH to default value (from defEnv)
7 => Could not set TMPDIR to default value (from defEnv)
8 => Could not set HOME to default value (from defEnv)
9 => Could not set LOGNAME to default value (from defEnv)
10 => Could not set SHELL to default value (from defEnv)
*/
int secureEnv(char **keepEnv, char **setEnv, char **defEnv) {
char *DkeepEnv[] = {NULL
};
char *DsetEnv[] = {NULL
};
char *DdefEnv[] = {"PATH",
"SHELL",
"LOGNAME",
"HOME",
NULL
};
char **cp, **pp, *eqLoc;
int keep;
char chrBuf[1048];
struct passwd *pwEnt = NULL;
uid_t theUID;
/* Take care of default values for the arguments. */
if(keepEnv == NULL)
keepEnv=DkeepEnv;
if(setEnv == NULL)
setEnv=DsetEnv;
if(defEnv == NULL)
defEnv=DdefEnv;
/* Delete all variables not listed in the keepEnv variable. */
for(cp=environ; *cp != NULL; cp++){
keep=0;
for(pp=keepEnv; (*pp != NULL) && (!keep); pp++) {
if(strstr(*cp, *pp) == *cp) { /* keepEnv line must start the environ line */
if(strlen(*cp) > strlen(*pp)) { /* environ line must be longer keepEnv line */
if((*cp)[strlen(*pp)] == '=') { /* Make sure it is a complete match */
keep = 1;
} /* end if */
} /* end if */
} /* end if */
} /* end for */
if(keep) {
} else {
eqLoc = strchr(*cp, '=');
if(eqLoc == NULL) {
return 1;
} /* end if */
if((eqLoc - *cp) > 512) {
return 2;
} /* end if */
strncpy(chrBuf, *cp, (eqLoc-*cp));
chrBuf[eqLoc-*cp] = '\0';
unsetenv(chrBuf);
cp=environ; /* Start over. */
} /* end if/else */
} /* end for */
/* Set all the variables given by setEnv. */
for(pp=setEnv; *pp != NULL ; pp++) {
if(putenv(*pp) < 0) {
return 3;
} /* end if */
} /* end for */
/* Now set all the variables in defEnv to the default values
specified by various system parameters. Supported are: PATH,
HOME, LOGNAME, SHELL, TMPDIR. */
for(pp=defEnv; *pp != NULL ; pp++) {
if (strcmp(*pp, "PATH") == 0) {
if(setenv("PATH", _PATH_STDPATH, 1) < 0)
return 6;
} else if(strcmp(*pp, "TMPDIR") == 0) {
if(setenv("TMPDIR", _PATH_TMP, 1) < 0)
return 7;
} else { /* Must be something from the passwd DB. */
theUID = getuid();
if((pwEnt = getpwuid(theUID)) == NULL) {
return 4;
} /* end if */
if(strcmp(*pp, "HOME") == 0) {
if(setenv("HOME", pwEnt->pw_dir, 1) < 0)
return 8;
} else if(strcmp(*pp, "LOGNAME") == 0) {
if(setenv("LOGNAME", pwEnt->pw_name, 1) < 0)
return 9;
} else if(strcmp(*pp, "SHELL") == 0) {
if(setenv("SHELL", pwEnt->pw_shell, 1) < 0)
return 10;
} /* end if/else */
} /* end if/else */
} /* end for */
return 0;
} /* end func secureEnv */
Generated by GNU Enscript 1.6.5.2.