1 /* $XConsortium: suid_exec.c /main/4 1995/12/12 15:09:04 rswiston $ */
2 /***************************************************************
6 * THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF *
7 * AT&T BELL LABORATORIES *
8 * AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN *
9 * ACCORDANCE WITH APPLICABLE AGREEMENTS *
11 * Copyright (c) 1995 AT&T Corp. *
12 * Unpublished & Not for Publication *
13 * All Rights Reserved *
15 * The copyright notice above does not evidence any *
16 * actual or intended publication of such source code *
18 * This software was created by the *
19 * Advanced Software Technology Department *
20 * AT&T Bell Laboratories *
22 * For further information contact *
23 * {research,attmail}!dgk *
25 ***************************************************************/
27 /* : : generated by proto : : */
29 #if !defined(__PROTO__)
30 #if defined(__STDC__) || defined(__cplusplus) || defined(_proto) || defined(c_plusplus)
31 #if defined(__cplusplus)
32 #define __MANGLE__ "C"
37 #define __PROTO__(x) x
39 #define __PARAM__(n,o) n
40 #if !defined(__STDC__) && !defined(__cplusplus)
41 #if !defined(c_plusplus)
52 #define __PROTO__(x) ()
53 #define __OTORP__(x) x
54 #define __PARAM__(n,o) o
62 #if defined(__cplusplus) || defined(c_plusplus)
63 #define __VARARG__ ...
67 #if defined(__STDARG__)
68 #define __VA_START__(p,a) va_start(p,a)
70 #define __VA_START__(p,a) va_start(p)
74 #include "FEATURE/externs"
79 #define SPECIAL 04100 /* setuid execute only by owner */
80 #define FDIN 10 /* must be same as /dev/fd below */
81 #define FDSYNC 11 /* used on sys5 to synchronize cleanup */
82 #define FDVERIFY 12 /* used to validate /tmp process */
83 #define BLKSIZE sizeof(char*)*1024
84 #define THISPROG SUIDEXECPATH
85 #define DEFSHELL "/bin/sh"
87 static void error_exit __PROTO__((const char*));
88 static int in_dir __PROTO__((const char*, const char*));
89 static int endsh __PROTO__((const char*));
91 static int mycopy __PROTO__((int, int));
92 static void maketemp __PROTO__((char*));
93 #endif /* _lib_setreuid */
95 static const char version[] = "\n@(#)suid_exec 12/28/93\n";
96 static const char badopen[] = "cannot open";
97 static const char badexec[] = "cannot exec";
98 static const char devfd[] = "/dev/fd/10"; /* must match FDIN above */
99 static char tmpname[] = "/tmp/SUIDXXXXXX";
100 static char **arglist;
103 static char *command;
104 static uid_t ruserid;
105 static uid_t euserid;
106 static gid_t rgroupid;
107 static gid_t egroupid;
108 static struct stat statb;
110 main __PARAM__((int argc,char *argv[]), (argc, argv)) __OTORP__(int argc;char *argv[];){
119 if((command = argv[1]) == 0)
124 egroupid = getegid();
126 #ifndef _lib_setreuid
128 if(strcmp(p,tmpname)==0)
130 /* At this point, the presumption is that we are the
131 * version of THISPROG copied into /tmp, with the owner,
132 * group, and setuid/gid bits correctly set. This copy of
133 * the program is executable by anyone, so we must be careful
134 * not to allow just any invocation of it to succeed, since
135 * it is setuid/gid. Validate the proper execution by
136 * examining the FDVERIFY file descriptor -- if it is owned
137 * by root and is mode SPECIAL, then this is proof that it was
138 * passed by a program with superuser privileges -- hence we
139 * can presume legitimacy. Otherwise, bail out, as we suspect
142 if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 ||
143 (statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0)
145 /* This enables the grandchild to clean up /tmp file */
147 /* Make sure that this is a valid invocation of the clone.
148 * Perhaps unnecessary, given FDVERIFY, but what the heck...
150 if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 ||
151 !S_ISREG(statb.st_mode))
153 if(ruserid != euserid &&
154 ((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid))
158 /* Make sure that this is the real setuid program, not the clone.
159 * It is possible by clever hacking to get past this point in the
160 * clone, but it doesn't do the hacker any good that I can see.
164 #endif /* _lib_setreuid */
165 /* Open the script for reading first and then validate it. This
166 * prevents someone from pulling a switcheroo while we are validating.
176 /* validate execution rights to this script */
177 if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL)
180 euserid = statb.st_uid;
181 /* do it the easy way if you can */
182 if(euserid == ruserid && egroupid == rgroupid)
184 if(access(p,X_OK) < 0)
189 /* have to check access on each component */
192 if(*p == '/' || *p == 0)
196 if(eaccess(argv[0],X_OK) < 0)
203 if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode))
205 if(stat(p, &statx) < 0 ||
206 statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev)
208 if(stat(THISPROG, &statx) < 0 ||
209 (statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev))
212 if(fcntl(n,F_DUPFD,FDIN) != FDIN)
216 /* compute the desired new effective user and group id */
220 if(statb.st_mode & S_ISUID)
221 effuid = statb.st_uid;
222 if(statb.st_mode & S_ISGID)
223 effgid = statb.st_gid;
225 /* see if group needs setting */
226 if(effgid != egroupid)
227 if(effgid != rgroupid || setgid(rgroupid) < 0)
230 /* now see if the uid needs setting */
233 if(effuid != ruserid)
238 if(effuid != ruserid || setuid(ruserid) < 0)
243 setids(mode, effuid, effgid);
245 /* only use SHELL if file is in trusted directory and ends in sh */
246 shell = getenv("SHELL");
247 if(shell == 0 || !endsh(shell) || (
248 !in_dir(CDE_INSTALLATION_TOP"/bin",shell) &&
249 !in_dir("/bin",shell) &&
250 !in_dir("/usr/bin",shell) &&
251 !in_dir("/usr/lbin",shell) &&
252 !in_dir("/usr/local/bin",shell)))
255 argv[1] = (char*)devfd;
261 * return true of shell ends in sh
264 static int endsh __PARAM__((register const char *shell), (shell)) __OTORP__(register const char *shell;){
267 if(*--shell != 'h' || *--shell != 's')
274 * return true of shell is in <dir> directory
277 static int in_dir __PARAM__((register const char *dir,register const char *shell), (dir, shell)) __OTORP__(register const char *dir;register const char *shell;){
280 if(*dir++ != *shell++)
283 /* return true if next character is a '/' */
287 static void error_exit __PARAM__((const char *message), (message)) __OTORP__(const char *message;){
288 sfprintf(sfstdout,"%s: %s\n",command,message);
294 * This version of access checks against effective uid and effective gid
297 eaccess __PARAM__((register const char *name, register int mode), (name, mode)) __OTORP__(register const char *name; register int mode;){
299 if (stat(name, &statb) == 0)
303 if(!S_ISREG(statb.st_mode) || mode != 1)
305 /* root needs execute permission for someone */
306 mode = (S_IXUSR|S_IXGRP|S_IXOTH);
308 else if(euserid == statb.st_uid)
310 else if(egroupid == statb.st_gid)
312 #ifdef _lib_getgroups
313 /* on some systems you can be in several groups */
316 static int maxgroups = 0;
322 if((maxgroups=getgroups(0, (gid_t *)NULL)) <= 0)
324 /* pre-POSIX system */
325 maxgroups=NGROUPS_MAX;
328 groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t));
329 n = getgroups(maxgroups,groups);
332 if(groups[n] == statb.st_gid)
338 free((void *)groups);
340 #endif /* _lib_getgroups */
341 if(statb.st_mode & mode)
348 setids __PARAM__((int mode,int owner,int group), (mode, owner, group)) __OTORP__(int mode;int owner;int group;){
350 setregid(rgroupid,group);
352 /* set effective uid even if S_ISUID is not set. This is because
353 * we are *really* executing EUID root at this point. Even if S_ISUID
354 * is not set, the value for owner that is passsed should be correct.
356 setreuid(ruserid,owner);
361 * This version of setids creats a /tmp file and copies itself into it.
362 * The "clone" file is made executable with appropriate suid/sgid bits.
363 * Finally, the clone is exec'ed. This file is unlinked by a grandchild
364 * of this program, who waits around until the text is free.
367 setids __PARAM__((int mode,uid_t owner,gid_t group), (mode, owner, group)) __OTORP__(int mode;uid_t owner;gid_t group;){
372 * Create a token to pass to the new program for validation.
373 * This token can only be procured by someone running with an
374 * effective userid of root, and hence gives the clone a way to
375 * certify that it was really invoked by THISPROG. Someone who
376 * is already root could spoof us, but why would they want to?
378 * Since we are root here, we must be careful: What if someone
379 * linked a valuable file to tmpname?
381 unlink(tmpname); /* should normally fail */
383 if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 ||
386 if((n = creat(tmpname, SPECIAL)) < 0 || unlink(tmpname) < 0)
392 if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY)
395 mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6);
396 /* create a pipe for synchronization */
404 { /* grandchild -- cleans up clone file */
405 signal(SIGHUP, SIG_IGN);
406 signal(SIGINT, SIG_IGN);
407 signal(SIGQUIT, SIG_IGN);
408 signal(SIGTERM, SIG_IGN);
409 read(pv[0],pv,1); /* wait for clone to close pipe */
410 while(unlink(tmpname) < 0 && errno == ETXTBSY)
418 /* Create a set[ug]id file that will become the clone.
419 * To make this atomic, without need for chown(), the
420 * child takes on desired user and group. The only
421 * downsize of this that I can see is that it may
422 * screw up some per- * user accounting.
424 if((m = open(THISPROG, O_RDONLY)) < 0)
426 if((mode & S_ISGID) && setgid(group) < 0)
428 if((mode & S_ISUID) && owner && setuid(owner) < 0)
431 if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0)
434 if((n = creat(tmpname, mode)) < 0)
437 /* populate the clone */
439 if(chmod(tmpname,mode) <0)
448 arglist[0] = (char*)tmpname;
450 /* move write end of pipe into FDSYNC */
454 if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC)
457 /* wait for child to die */
458 while((m = wait(0)) != n)
459 if(m == -1 && errno != EINTR)
461 /* Kill any setuid status at this point. That way, if the
462 * clone is not setuid, we won't exec it as root. Also, don't
463 * neglect to consider that someone could have switched the
466 if(setuid(ruserid) < 0)
468 execv(tmpname,arglist);
474 * create a unique name into the <template>
477 static void maketemp __PARAM__((char *template), (template)) __OTORP__(char *template;){
478 register char *cp = template;
479 register pid_t n = getpid();
480 /* skip to end of string */
482 /* convert process id to string */
485 *--cp = (n%10) + '0';
492 * copy THISPROG into the open file number <fdo> and close <fdo>
495 static int mycopy __PARAM__((int fdi, int fdo), (fdi, fdo)) __OTORP__(int fdi; int fdo;){
496 char buffer[BLKSIZE];
499 while((n = read(fdi,buffer,BLKSIZE)) > 0)
500 if(write(fdo,buffer,n) != n)
507 #endif /* _lib_setreuid */