14887Schin /*********************************************************************** 24887Schin * * 34887Schin * This software is part of the ast package * 4*8462SApril.Chin@Sun.COM * Copyright (c) 1982-2008 AT&T Intellectual Property * 54887Schin * and is licensed under the * 64887Schin * Common Public License, Version 1.0 * 7*8462SApril.Chin@Sun.COM * by AT&T Intellectual Property * 84887Schin * * 94887Schin * A copy of the License is available at * 104887Schin * http://www.opensource.org/licenses/cpl1.0.txt * 114887Schin * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 124887Schin * * 134887Schin * Information and Software Systems Research * 144887Schin * AT&T Research * 154887Schin * Florham Park NJ * 164887Schin * * 174887Schin * David Korn <dgk@research.att.com> * 184887Schin * * 194887Schin ***********************************************************************/ 204887Schin #pragma prototyped 214887Schin /* 224887Schin * This is a program to execute 'execute only' and suid/sgid shell scripts. 234887Schin * This program must be owned by root and must have the set uid bit set. 244887Schin * It must not have the set group id bit set. This program must be installed 254887Schin * where the define parameter THISPROG indicates to work correctly on system V 264887Schin * 274887Schin * Written by David Korn 284887Schin * AT&T Labs 294887Schin * Enhanced by Rob Stampfli 304887Schin */ 314887Schin 324887Schin /* The file name of the script to execute is argv[0] 334887Schin * Argv[1] is the program name 344887Schin * The basic idea is to open the script as standard input, set the effective 354887Schin * user and group id correctly, and then exec the shell. 364887Schin * The complicated part is getting the effective uid of the caller and 374887Schin * setting the effective uid/gid. The program which execs this program 384887Schin * may pass file descriptor FDIN as an open file with mode SPECIAL if 394887Schin * the effective user id is not the real user id. The effective 404887Schin * user id for authentication purposes will be the owner of this 414887Schin * open file. On systems without the setreuid() call, e[ug]id is set 424887Schin * by copying this program to a /tmp/file, making it a suid and/or sgid 434887Schin * program, and then execing this program. 444887Schin * A forked version of this program waits until it can unlink the /tmp 454887Schin * file and then exits. Actually, we fork() twice so the parent can 464887Schin * wait for the child to complete. A pipe is used to guarantee that we 474887Schin * do not remove the /tmp file too soon. 484887Schin */ 494887Schin 504887Schin #include <ast.h> 514887Schin #include "FEATURE/externs" 524887Schin #include <ls.h> 534887Schin #include <sig.h> 544887Schin #include <error.h> 554887Schin #include <sys/wait.h> 564887Schin #include "version.h" 574887Schin 584887Schin #define SPECIAL 04100 /* setuid execute only by owner */ 594887Schin #define FDIN 10 /* must be same as /dev/fd below */ 604887Schin #undef FDSYNC 614887Schin #define FDSYNC 11 /* used on sys5 to synchronize cleanup */ 624887Schin #define FDVERIFY 12 /* used to validate /tmp process */ 634887Schin #undef BLKSIZE 644887Schin #define BLKSIZE sizeof(char*)*1024 654887Schin #define THISPROG "/etc/suid_exec" 664887Schin #define DEFSHELL "/bin/sh" 674887Schin 684887Schin static void error_exit(const char*); 694887Schin static int in_dir(const char*, const char*); 704887Schin static int endsh(const char*); 714887Schin #ifndef _lib_setregid 724887Schin # undef _lib_setreuid 734887Schin #endif 744887Schin #ifndef _lib_setreuid 754887Schin static void setids(int,uid_t,gid_t); 764887Schin static int mycopy(int, int); 774887Schin static void maketemp(char*); 784887Schin #else 794887Schin static void setids(int,int,int); 804887Schin #endif /* _lib_setreuid */ 814887Schin 824887Schin static const char version[] = "\n@(#)$Id: suid_exec "SH_RELEASE" $\n"; 834887Schin static const char badopen[] = "cannot open"; 844887Schin static const char badexec[] = "cannot exec"; 854887Schin static const char devfd[] = "/dev/fd/10"; /* must match FDIN above */ 864887Schin static char tmpname[] = "/tmp/SUIDXXXXXX"; 874887Schin static char **arglist; 884887Schin 894887Schin static char *shell; 904887Schin static char *command; 914887Schin static uid_t ruserid; 924887Schin static uid_t euserid; 934887Schin static gid_t rgroupid; 944887Schin static gid_t egroupid; 954887Schin static struct stat statb; 964887Schin 974887Schin int main(int argc,char *argv[]) 984887Schin { 994887Schin register int m,n; 1004887Schin register char *p; 1014887Schin struct stat statx; 1024887Schin int mode; 1034887Schin uid_t effuid; 1044887Schin gid_t effgid; 1054887Schin NOT_USED(argc); 1064887Schin arglist = argv; 1074887Schin if((command = argv[1]) == 0) 1084887Schin error_exit(badexec); 1094887Schin ruserid = getuid(); 1104887Schin euserid = geteuid(); 1114887Schin rgroupid = getgid(); 1124887Schin egroupid = getegid(); 1134887Schin p = argv[0]; 1144887Schin #ifndef _lib_setreuid 1154887Schin maketemp(tmpname); 1164887Schin if(strcmp(p,tmpname)==0) 1174887Schin { 1184887Schin /* At this point, the presumption is that we are the 1194887Schin * version of THISPROG copied into /tmp, with the owner, 1204887Schin * group, and setuid/gid bits correctly set. This copy of 1214887Schin * the program is executable by anyone, so we must be careful 1224887Schin * not to allow just any invocation of it to succeed, since 1234887Schin * it is setuid/gid. Validate the proper execution by 1244887Schin * examining the FDVERIFY file descriptor -- if it is owned 1254887Schin * by root and is mode SPECIAL, then this is proof that it was 1264887Schin * passed by a program with superuser privileges -- hence we 1274887Schin * can presume legitimacy. Otherwise, bail out, as we suspect 1284887Schin * an impostor. 1294887Schin */ 1304887Schin if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 || 1314887Schin (statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0) 1324887Schin error_exit(badexec); 1334887Schin /* This enables the grandchild to clean up /tmp file */ 1344887Schin close(FDSYNC); 1354887Schin /* Make sure that this is a valid invocation of the clone. 1364887Schin * Perhaps unnecessary, given FDVERIFY, but what the heck... 1374887Schin */ 1384887Schin if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 || 1394887Schin !S_ISREG(statb.st_mode)) 1404887Schin error_exit(badexec); 1414887Schin if(ruserid != euserid && 1424887Schin ((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid)) 1434887Schin error_exit(badexec); 1444887Schin goto exec; 1454887Schin } 1464887Schin /* Make sure that this is the real setuid program, not the clone. 1474887Schin * It is possible by clever hacking to get past this point in the 1484887Schin * clone, but it doesn't do the hacker any good that I can see. 1494887Schin */ 1504887Schin if(euserid) 1514887Schin error_exit(badexec); 1524887Schin #endif /* _lib_setreuid */ 1534887Schin /* Open the script for reading first and then validate it. This 1544887Schin * prevents someone from pulling a switcheroo while we are validating. 1554887Schin */ 1564887Schin n = open(p,0); 1574887Schin if(n == FDIN) 1584887Schin { 1594887Schin n = dup(n); 1604887Schin close(FDIN); 1614887Schin } 1624887Schin if(n < 0) 1634887Schin error_exit(badopen); 1644887Schin /* validate execution rights to this script */ 1654887Schin if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL) 1664887Schin euserid = ruserid; 1674887Schin else 1684887Schin euserid = statb.st_uid; 1694887Schin /* do it the easy way if you can */ 1704887Schin if(euserid == ruserid && egroupid == rgroupid) 1714887Schin { 1724887Schin if(access(p,X_OK) < 0) 1734887Schin error_exit(badexec); 1744887Schin } 1754887Schin else 1764887Schin { 1774887Schin /* have to check access on each component */ 1784887Schin while(*p++) 1794887Schin { 1804887Schin if(*p == '/' || *p == 0) 1814887Schin { 1824887Schin m = *p; 1834887Schin *p = 0; 1844887Schin if(eaccess(argv[0],X_OK) < 0) 1854887Schin error_exit(badexec); 1864887Schin *p = m; 1874887Schin } 1884887Schin } 1894887Schin p = argv[0]; 1904887Schin } 1914887Schin if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode)) 1924887Schin error_exit(badopen); 1934887Schin if(stat(p, &statx) < 0 || 1944887Schin statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev) 1954887Schin error_exit(badexec); 1964887Schin if(stat(THISPROG, &statx) < 0 || 1974887Schin (statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev)) 1984887Schin error_exit(badexec); 1994887Schin close(FDIN); 2004887Schin if(fcntl(n,F_DUPFD,FDIN) != FDIN) 2014887Schin error_exit(badexec); 2024887Schin close(n); 2034887Schin 2044887Schin /* compute the desired new effective user and group id */ 2054887Schin effuid = euserid; 2064887Schin effgid = egroupid; 2074887Schin mode = 0; 2084887Schin if(statb.st_mode & S_ISUID) 2094887Schin effuid = statb.st_uid; 2104887Schin if(statb.st_mode & S_ISGID) 2114887Schin effgid = statb.st_gid; 2124887Schin 2134887Schin /* see if group needs setting */ 2144887Schin if(effgid != egroupid) 2154887Schin if(effgid != rgroupid || setgid(rgroupid) < 0) 2164887Schin mode = S_ISGID; 2174887Schin 2184887Schin /* now see if the uid needs setting */ 2194887Schin if(mode) 2204887Schin { 2214887Schin if(effuid != ruserid) 2224887Schin mode |= S_ISUID; 2234887Schin } 2244887Schin else if(effuid) 2254887Schin { 2264887Schin if(effuid != ruserid || setuid(ruserid) < 0) 2274887Schin mode = S_ISUID; 2284887Schin } 2294887Schin 2304887Schin if(mode) 2314887Schin setids(mode, effuid, effgid); 2324887Schin #ifndef _lib_setreuid 2334887Schin exec: 2344887Schin #endif /* _lib_setreuid */ 2354887Schin /* only use SHELL if file is in trusted directory and ends in sh */ 2364887Schin shell = getenv("SHELL"); 2374887Schin if(shell == 0 || !endsh(shell) || ( 2384887Schin !in_dir("/bin",shell) && 2394887Schin !in_dir("/usr/bin",shell) && 2404887Schin !in_dir("/usr/lbin",shell) && 2414887Schin !in_dir("/usr/local/bin",shell))) 2424887Schin shell = DEFSHELL; 2434887Schin argv[0] = command; 2444887Schin argv[1] = (char*)devfd; 2454887Schin execv(shell,argv); 2464887Schin error_exit(badexec); 2474887Schin } 2484887Schin 2494887Schin /* 2504887Schin * return true of shell ends in sh of ksh 2514887Schin */ 2524887Schin 2534887Schin static int endsh(register const char *shell) 2544887Schin { 2554887Schin while(*shell) 2564887Schin shell++; 2574887Schin if(*--shell != 'h' || *--shell != 's') 2584887Schin return(0); 2594887Schin if(*--shell=='/') 2604887Schin return(1); 2614887Schin if(*shell=='k' && *--shell=='/') 2624887Schin return(1); 2634887Schin return(0); 2644887Schin } 2654887Schin 2664887Schin 2674887Schin /* 2684887Schin * return true of shell is in <dir> directory 2694887Schin */ 2704887Schin 2714887Schin static int in_dir(register const char *dir,register const char *shell) 2724887Schin { 2734887Schin while(*dir) 2744887Schin { 2754887Schin if(*dir++ != *shell++) 2764887Schin return(0); 2774887Schin } 2784887Schin /* return true if next character is a '/' */ 2794887Schin return(*shell=='/'); 2804887Schin } 2814887Schin 2824887Schin static void error_exit(const char *message) 2834887Schin { 2844887Schin sfprintf(sfstdout,"%s: %s\n",command,message); 2854887Schin exit(126); 2864887Schin } 2874887Schin 2884887Schin 2894887Schin /* 2904887Schin * This version of access checks against effective uid and effective gid 2914887Schin */ 2924887Schin 2934887Schin int eaccess(register const char *name, register int mode) 2944887Schin { 2954887Schin struct stat statb; 2964887Schin if (stat(name, &statb) == 0) 2974887Schin { 2984887Schin if(euserid == 0) 2994887Schin { 3004887Schin if(!S_ISREG(statb.st_mode) || mode != 1) 3014887Schin return(0); 3024887Schin /* root needs execute permission for someone */ 3034887Schin mode = (S_IXUSR|S_IXGRP|S_IXOTH); 3044887Schin } 3054887Schin else if(euserid == statb.st_uid) 3064887Schin mode <<= 6; 3074887Schin else if(egroupid == statb.st_gid) 3084887Schin mode <<= 3; 3094887Schin #ifdef _lib_getgroups 3104887Schin /* on some systems you can be in several groups */ 3114887Schin else 3124887Schin { 3134887Schin static int maxgroups; 3144887Schin gid_t *groups=0; 3154887Schin register int n; 3164887Schin if(maxgroups==0) 3174887Schin { 3184887Schin /* first time */ 3194887Schin if((maxgroups=getgroups(0,groups)) < 0) 3204887Schin { 3214887Schin /* pre-POSIX system */ 3224887Schin maxgroups=NGROUPS_MAX; 3234887Schin } 3244887Schin } 3254887Schin groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t)); 3264887Schin n = getgroups(maxgroups,groups); 3274887Schin while(--n >= 0) 3284887Schin { 3294887Schin if(groups[n] == statb.st_gid) 3304887Schin { 3314887Schin mode <<= 3; 3324887Schin break; 3334887Schin } 3344887Schin } 3354887Schin } 3364887Schin #endif /* _lib_getgroups */ 3374887Schin if(statb.st_mode & mode) 3384887Schin return(0); 3394887Schin } 3404887Schin return(-1); 3414887Schin } 3424887Schin 3434887Schin #ifdef _lib_setreuid 3444887Schin static void setids(int mode,int owner,int group) 3454887Schin { 3464887Schin if(mode & S_ISGID) 3474887Schin setregid(rgroupid,group); 3484887Schin 3494887Schin /* set effective uid even if S_ISUID is not set. This is because 3504887Schin * we are *really* executing EUID root at this point. Even if S_ISUID 3514887Schin * is not set, the value for owner that is passsed should be correct. 3524887Schin */ 3534887Schin setreuid(ruserid,owner); 3544887Schin } 3554887Schin 3564887Schin #else 3574887Schin /* 3584887Schin * This version of setids creats a /tmp file and copies itself into it. 3594887Schin * The "clone" file is made executable with appropriate suid/sgid bits. 3604887Schin * Finally, the clone is exec'ed. This file is unlinked by a grandchild 3614887Schin * of this program, who waits around until the text is free. 3624887Schin */ 3634887Schin 3644887Schin static void setids(int mode,uid_t owner,gid_t group) 3654887Schin { 3664887Schin register int n,m; 3674887Schin int pv[2]; 3684887Schin 3694887Schin /* 3704887Schin * Create a token to pass to the new program for validation. 3714887Schin * This token can only be procured by someone running with an 3724887Schin * effective userid of root, and hence gives the clone a way to 3734887Schin * certify that it was really invoked by THISPROG. Someone who 3744887Schin * is already root could spoof us, but why would they want to? 3754887Schin * 3764887Schin * Since we are root here, we must be careful: What if someone 3774887Schin * linked a valuable file to tmpname? 3784887Schin */ 3794887Schin unlink(tmpname); /* should normally fail */ 3804887Schin #ifdef O_EXCL 3814887Schin if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 || 3824887Schin unlink(tmpname) < 0) 3834887Schin #else 3844887Schin if((n = open(tmpname, O_WRONLY | O_CREAT ,SPECIAL)) < 0 || unlink(tmpname) < 0) 3854887Schin #endif 3864887Schin error_exit(badexec); 3874887Schin if(n != FDVERIFY) 3884887Schin { 3894887Schin close(FDVERIFY); 3904887Schin if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY) 3914887Schin error_exit(badexec); 3924887Schin } 3934887Schin mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6); 3944887Schin /* create a pipe for synchronization */ 3954887Schin if(pipe(pv) < 0) 3964887Schin error_exit(badexec); 3974887Schin if((n=fork()) == 0) 3984887Schin { /* child */ 3994887Schin close(FDVERIFY); 4004887Schin close(pv[1]); 4014887Schin if((n=fork()) == 0) 4024887Schin { /* grandchild -- cleans up clone file */ 4034887Schin signal(SIGHUP, SIG_IGN); 4044887Schin signal(SIGINT, SIG_IGN); 4054887Schin signal(SIGQUIT, SIG_IGN); 4064887Schin signal(SIGTERM, SIG_IGN); 4074887Schin read(pv[0],pv,1); /* wait for clone to close pipe */ 4084887Schin while(unlink(tmpname) < 0 && errno == ETXTBSY) 4094887Schin sleep(1); 4104887Schin exit(0); 4114887Schin } 4124887Schin else if(n == -1) 4134887Schin exit(1); 4144887Schin else 4154887Schin { 4164887Schin /* Create a set[ug]id file that will become the clone. 4174887Schin * To make this atomic, without need for chown(), the 4184887Schin * child takes on desired user and group. The only 4194887Schin * downsize of this that I can see is that it may 4204887Schin * screw up some per- * user accounting. 4214887Schin */ 4224887Schin if((m = open(THISPROG, O_RDONLY)) < 0) 4234887Schin exit(1); 4244887Schin if((mode & S_ISGID) && setgid(group) < 0) 4254887Schin exit(1); 4264887Schin if((mode & S_ISUID) && owner && setuid(owner) < 0) 4274887Schin exit(1); 4284887Schin #ifdef O_EXCL 4294887Schin if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, mode)) < 0) 4304887Schin #else 4314887Schin unlink(tmpname); 4324887Schin if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0) 4334887Schin #endif /* O_EXCL */ 4344887Schin exit(1); 4354887Schin /* populate the clone */ 4364887Schin m = mycopy(m,n); 4374887Schin if(chmod(tmpname,mode) <0) 4384887Schin exit(1); 4394887Schin exit(m); 4404887Schin } 4414887Schin } 4424887Schin else if(n == -1) 4434887Schin error_exit(badexec); 4444887Schin else 4454887Schin { 4464887Schin arglist[0] = (char*)tmpname; 4474887Schin close(pv[0]); 4484887Schin /* move write end of pipe into FDSYNC */ 4494887Schin if(pv[1] != FDSYNC) 4504887Schin { 4514887Schin close(FDSYNC); 4524887Schin if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC) 4534887Schin error_exit(badexec); 4544887Schin } 4554887Schin /* wait for child to die */ 4564887Schin while((m = wait(0)) != n) 4574887Schin if(m == -1 && errno != EINTR) 4584887Schin break; 4594887Schin /* Kill any setuid status at this point. That way, if the 4604887Schin * clone is not setuid, we won't exec it as root. Also, don't 4614887Schin * neglect to consider that someone could have switched the 4624887Schin * clone file on us. 4634887Schin */ 4644887Schin if(setuid(ruserid) < 0) 4654887Schin error_exit(badexec); 4664887Schin execv(tmpname,arglist); 4674887Schin error_exit(badexec); 4684887Schin } 4694887Schin } 4704887Schin 4714887Schin /* 4724887Schin * create a unique name into the <template> 4734887Schin */ 4744887Schin 4754887Schin static void maketemp(char *template) 4764887Schin { 4774887Schin register char *cp = template; 4784887Schin register pid_t n = getpid(); 4794887Schin /* skip to end of string */ 4804887Schin while(*++cp); 4814887Schin /* convert process id to string */ 4824887Schin while(n > 0) 4834887Schin { 4844887Schin *--cp = (n%10) + '0'; 4854887Schin n /= 10; 4864887Schin } 4874887Schin 4884887Schin } 4894887Schin 4904887Schin /* 4914887Schin * copy THISPROG into the open file number <fdo> and close <fdo> 4924887Schin */ 4934887Schin 4944887Schin static int mycopy(int fdi, int fdo) 4954887Schin { 4964887Schin char buffer[BLKSIZE]; 4974887Schin register int n; 4984887Schin 4994887Schin while((n = read(fdi,buffer,BLKSIZE)) > 0) 5004887Schin if(write(fdo,buffer,n) != n) 5014887Schin break; 5024887Schin close(fdi); 5034887Schin close(fdo); 5044887Schin return n; 5054887Schin } 5064887Schin 5074887Schin #endif /* _lib_setreuid */ 5084887Schin 5094887Schin 510