1*4887Schin /*********************************************************************** 2*4887Schin * * 3*4887Schin * This software is part of the ast package * 4*4887Schin * Copyright (c) 1982-2007 AT&T Knowledge Ventures * 5*4887Schin * and is licensed under the * 6*4887Schin * Common Public License, Version 1.0 * 7*4887Schin * by AT&T Knowledge Ventures * 8*4887Schin * * 9*4887Schin * A copy of the License is available at * 10*4887Schin * http://www.opensource.org/licenses/cpl1.0.txt * 11*4887Schin * (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) * 12*4887Schin * * 13*4887Schin * Information and Software Systems Research * 14*4887Schin * AT&T Research * 15*4887Schin * Florham Park NJ * 16*4887Schin * * 17*4887Schin * David Korn <dgk@research.att.com> * 18*4887Schin * * 19*4887Schin ***********************************************************************/ 20*4887Schin #pragma prototyped 21*4887Schin /* 22*4887Schin * This is a program to execute 'execute only' and suid/sgid shell scripts. 23*4887Schin * This program must be owned by root and must have the set uid bit set. 24*4887Schin * It must not have the set group id bit set. This program must be installed 25*4887Schin * where the define parameter THISPROG indicates to work correctly on system V 26*4887Schin * 27*4887Schin * Written by David Korn 28*4887Schin * AT&T Labs 29*4887Schin * Enhanced by Rob Stampfli 30*4887Schin */ 31*4887Schin 32*4887Schin /* The file name of the script to execute is argv[0] 33*4887Schin * Argv[1] is the program name 34*4887Schin * The basic idea is to open the script as standard input, set the effective 35*4887Schin * user and group id correctly, and then exec the shell. 36*4887Schin * The complicated part is getting the effective uid of the caller and 37*4887Schin * setting the effective uid/gid. The program which execs this program 38*4887Schin * may pass file descriptor FDIN as an open file with mode SPECIAL if 39*4887Schin * the effective user id is not the real user id. The effective 40*4887Schin * user id for authentication purposes will be the owner of this 41*4887Schin * open file. On systems without the setreuid() call, e[ug]id is set 42*4887Schin * by copying this program to a /tmp/file, making it a suid and/or sgid 43*4887Schin * program, and then execing this program. 44*4887Schin * A forked version of this program waits until it can unlink the /tmp 45*4887Schin * file and then exits. Actually, we fork() twice so the parent can 46*4887Schin * wait for the child to complete. A pipe is used to guarantee that we 47*4887Schin * do not remove the /tmp file too soon. 48*4887Schin */ 49*4887Schin 50*4887Schin #include <ast.h> 51*4887Schin #include "FEATURE/externs" 52*4887Schin #include <ls.h> 53*4887Schin #include <sig.h> 54*4887Schin #include <error.h> 55*4887Schin #include <sys/wait.h> 56*4887Schin #include "version.h" 57*4887Schin 58*4887Schin #define SPECIAL 04100 /* setuid execute only by owner */ 59*4887Schin #define FDIN 10 /* must be same as /dev/fd below */ 60*4887Schin #undef FDSYNC 61*4887Schin #define FDSYNC 11 /* used on sys5 to synchronize cleanup */ 62*4887Schin #define FDVERIFY 12 /* used to validate /tmp process */ 63*4887Schin #undef BLKSIZE 64*4887Schin #define BLKSIZE sizeof(char*)*1024 65*4887Schin #define THISPROG "/etc/suid_exec" 66*4887Schin #define DEFSHELL "/bin/sh" 67*4887Schin 68*4887Schin static void error_exit(const char*); 69*4887Schin static int in_dir(const char*, const char*); 70*4887Schin static int endsh(const char*); 71*4887Schin #ifndef _lib_setregid 72*4887Schin # undef _lib_setreuid 73*4887Schin #endif 74*4887Schin #ifndef _lib_setreuid 75*4887Schin static void setids(int,uid_t,gid_t); 76*4887Schin static int mycopy(int, int); 77*4887Schin static void maketemp(char*); 78*4887Schin #else 79*4887Schin static void setids(int,int,int); 80*4887Schin #endif /* _lib_setreuid */ 81*4887Schin 82*4887Schin static const char version[] = "\n@(#)$Id: suid_exec "SH_RELEASE" $\n"; 83*4887Schin static const char badopen[] = "cannot open"; 84*4887Schin static const char badexec[] = "cannot exec"; 85*4887Schin static const char devfd[] = "/dev/fd/10"; /* must match FDIN above */ 86*4887Schin static char tmpname[] = "/tmp/SUIDXXXXXX"; 87*4887Schin static char **arglist; 88*4887Schin 89*4887Schin static char *shell; 90*4887Schin static char *command; 91*4887Schin static uid_t ruserid; 92*4887Schin static uid_t euserid; 93*4887Schin static gid_t rgroupid; 94*4887Schin static gid_t egroupid; 95*4887Schin static struct stat statb; 96*4887Schin 97*4887Schin int main(int argc,char *argv[]) 98*4887Schin { 99*4887Schin register int m,n; 100*4887Schin register char *p; 101*4887Schin struct stat statx; 102*4887Schin int mode; 103*4887Schin uid_t effuid; 104*4887Schin gid_t effgid; 105*4887Schin NOT_USED(argc); 106*4887Schin arglist = argv; 107*4887Schin if((command = argv[1]) == 0) 108*4887Schin error_exit(badexec); 109*4887Schin ruserid = getuid(); 110*4887Schin euserid = geteuid(); 111*4887Schin rgroupid = getgid(); 112*4887Schin egroupid = getegid(); 113*4887Schin p = argv[0]; 114*4887Schin #ifndef _lib_setreuid 115*4887Schin maketemp(tmpname); 116*4887Schin if(strcmp(p,tmpname)==0) 117*4887Schin { 118*4887Schin /* At this point, the presumption is that we are the 119*4887Schin * version of THISPROG copied into /tmp, with the owner, 120*4887Schin * group, and setuid/gid bits correctly set. This copy of 121*4887Schin * the program is executable by anyone, so we must be careful 122*4887Schin * not to allow just any invocation of it to succeed, since 123*4887Schin * it is setuid/gid. Validate the proper execution by 124*4887Schin * examining the FDVERIFY file descriptor -- if it is owned 125*4887Schin * by root and is mode SPECIAL, then this is proof that it was 126*4887Schin * passed by a program with superuser privileges -- hence we 127*4887Schin * can presume legitimacy. Otherwise, bail out, as we suspect 128*4887Schin * an impostor. 129*4887Schin */ 130*4887Schin if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 || 131*4887Schin (statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0) 132*4887Schin error_exit(badexec); 133*4887Schin /* This enables the grandchild to clean up /tmp file */ 134*4887Schin close(FDSYNC); 135*4887Schin /* Make sure that this is a valid invocation of the clone. 136*4887Schin * Perhaps unnecessary, given FDVERIFY, but what the heck... 137*4887Schin */ 138*4887Schin if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 || 139*4887Schin !S_ISREG(statb.st_mode)) 140*4887Schin error_exit(badexec); 141*4887Schin if(ruserid != euserid && 142*4887Schin ((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid)) 143*4887Schin error_exit(badexec); 144*4887Schin goto exec; 145*4887Schin } 146*4887Schin /* Make sure that this is the real setuid program, not the clone. 147*4887Schin * It is possible by clever hacking to get past this point in the 148*4887Schin * clone, but it doesn't do the hacker any good that I can see. 149*4887Schin */ 150*4887Schin if(euserid) 151*4887Schin error_exit(badexec); 152*4887Schin #endif /* _lib_setreuid */ 153*4887Schin /* Open the script for reading first and then validate it. This 154*4887Schin * prevents someone from pulling a switcheroo while we are validating. 155*4887Schin */ 156*4887Schin n = open(p,0); 157*4887Schin if(n == FDIN) 158*4887Schin { 159*4887Schin n = dup(n); 160*4887Schin close(FDIN); 161*4887Schin } 162*4887Schin if(n < 0) 163*4887Schin error_exit(badopen); 164*4887Schin /* validate execution rights to this script */ 165*4887Schin if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL) 166*4887Schin euserid = ruserid; 167*4887Schin else 168*4887Schin euserid = statb.st_uid; 169*4887Schin /* do it the easy way if you can */ 170*4887Schin if(euserid == ruserid && egroupid == rgroupid) 171*4887Schin { 172*4887Schin if(access(p,X_OK) < 0) 173*4887Schin error_exit(badexec); 174*4887Schin } 175*4887Schin else 176*4887Schin { 177*4887Schin /* have to check access on each component */ 178*4887Schin while(*p++) 179*4887Schin { 180*4887Schin if(*p == '/' || *p == 0) 181*4887Schin { 182*4887Schin m = *p; 183*4887Schin *p = 0; 184*4887Schin if(eaccess(argv[0],X_OK) < 0) 185*4887Schin error_exit(badexec); 186*4887Schin *p = m; 187*4887Schin } 188*4887Schin } 189*4887Schin p = argv[0]; 190*4887Schin } 191*4887Schin if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode)) 192*4887Schin error_exit(badopen); 193*4887Schin if(stat(p, &statx) < 0 || 194*4887Schin statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev) 195*4887Schin error_exit(badexec); 196*4887Schin if(stat(THISPROG, &statx) < 0 || 197*4887Schin (statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev)) 198*4887Schin error_exit(badexec); 199*4887Schin close(FDIN); 200*4887Schin if(fcntl(n,F_DUPFD,FDIN) != FDIN) 201*4887Schin error_exit(badexec); 202*4887Schin close(n); 203*4887Schin 204*4887Schin /* compute the desired new effective user and group id */ 205*4887Schin effuid = euserid; 206*4887Schin effgid = egroupid; 207*4887Schin mode = 0; 208*4887Schin if(statb.st_mode & S_ISUID) 209*4887Schin effuid = statb.st_uid; 210*4887Schin if(statb.st_mode & S_ISGID) 211*4887Schin effgid = statb.st_gid; 212*4887Schin 213*4887Schin /* see if group needs setting */ 214*4887Schin if(effgid != egroupid) 215*4887Schin if(effgid != rgroupid || setgid(rgroupid) < 0) 216*4887Schin mode = S_ISGID; 217*4887Schin 218*4887Schin /* now see if the uid needs setting */ 219*4887Schin if(mode) 220*4887Schin { 221*4887Schin if(effuid != ruserid) 222*4887Schin mode |= S_ISUID; 223*4887Schin } 224*4887Schin else if(effuid) 225*4887Schin { 226*4887Schin if(effuid != ruserid || setuid(ruserid) < 0) 227*4887Schin mode = S_ISUID; 228*4887Schin } 229*4887Schin 230*4887Schin if(mode) 231*4887Schin setids(mode, effuid, effgid); 232*4887Schin #ifndef _lib_setreuid 233*4887Schin exec: 234*4887Schin #endif /* _lib_setreuid */ 235*4887Schin /* only use SHELL if file is in trusted directory and ends in sh */ 236*4887Schin shell = getenv("SHELL"); 237*4887Schin if(shell == 0 || !endsh(shell) || ( 238*4887Schin !in_dir("/bin",shell) && 239*4887Schin !in_dir("/usr/bin",shell) && 240*4887Schin !in_dir("/usr/lbin",shell) && 241*4887Schin !in_dir("/usr/local/bin",shell))) 242*4887Schin shell = DEFSHELL; 243*4887Schin argv[0] = command; 244*4887Schin argv[1] = (char*)devfd; 245*4887Schin execv(shell,argv); 246*4887Schin error_exit(badexec); 247*4887Schin } 248*4887Schin 249*4887Schin /* 250*4887Schin * return true of shell ends in sh of ksh 251*4887Schin */ 252*4887Schin 253*4887Schin static int endsh(register const char *shell) 254*4887Schin { 255*4887Schin while(*shell) 256*4887Schin shell++; 257*4887Schin if(*--shell != 'h' || *--shell != 's') 258*4887Schin return(0); 259*4887Schin if(*--shell=='/') 260*4887Schin return(1); 261*4887Schin if(*shell=='k' && *--shell=='/') 262*4887Schin return(1); 263*4887Schin return(0); 264*4887Schin } 265*4887Schin 266*4887Schin 267*4887Schin /* 268*4887Schin * return true of shell is in <dir> directory 269*4887Schin */ 270*4887Schin 271*4887Schin static int in_dir(register const char *dir,register const char *shell) 272*4887Schin { 273*4887Schin while(*dir) 274*4887Schin { 275*4887Schin if(*dir++ != *shell++) 276*4887Schin return(0); 277*4887Schin } 278*4887Schin /* return true if next character is a '/' */ 279*4887Schin return(*shell=='/'); 280*4887Schin } 281*4887Schin 282*4887Schin static void error_exit(const char *message) 283*4887Schin { 284*4887Schin sfprintf(sfstdout,"%s: %s\n",command,message); 285*4887Schin exit(126); 286*4887Schin } 287*4887Schin 288*4887Schin 289*4887Schin /* 290*4887Schin * This version of access checks against effective uid and effective gid 291*4887Schin */ 292*4887Schin 293*4887Schin int eaccess(register const char *name, register int mode) 294*4887Schin { 295*4887Schin struct stat statb; 296*4887Schin if (stat(name, &statb) == 0) 297*4887Schin { 298*4887Schin if(euserid == 0) 299*4887Schin { 300*4887Schin if(!S_ISREG(statb.st_mode) || mode != 1) 301*4887Schin return(0); 302*4887Schin /* root needs execute permission for someone */ 303*4887Schin mode = (S_IXUSR|S_IXGRP|S_IXOTH); 304*4887Schin } 305*4887Schin else if(euserid == statb.st_uid) 306*4887Schin mode <<= 6; 307*4887Schin else if(egroupid == statb.st_gid) 308*4887Schin mode <<= 3; 309*4887Schin #ifdef _lib_getgroups 310*4887Schin /* on some systems you can be in several groups */ 311*4887Schin else 312*4887Schin { 313*4887Schin static int maxgroups; 314*4887Schin gid_t *groups=0; 315*4887Schin register int n; 316*4887Schin if(maxgroups==0) 317*4887Schin { 318*4887Schin /* first time */ 319*4887Schin if((maxgroups=getgroups(0,groups)) < 0) 320*4887Schin { 321*4887Schin /* pre-POSIX system */ 322*4887Schin maxgroups=NGROUPS_MAX; 323*4887Schin } 324*4887Schin } 325*4887Schin groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t)); 326*4887Schin n = getgroups(maxgroups,groups); 327*4887Schin while(--n >= 0) 328*4887Schin { 329*4887Schin if(groups[n] == statb.st_gid) 330*4887Schin { 331*4887Schin mode <<= 3; 332*4887Schin break; 333*4887Schin } 334*4887Schin } 335*4887Schin } 336*4887Schin #endif /* _lib_getgroups */ 337*4887Schin if(statb.st_mode & mode) 338*4887Schin return(0); 339*4887Schin } 340*4887Schin return(-1); 341*4887Schin } 342*4887Schin 343*4887Schin #ifdef _lib_setreuid 344*4887Schin static void setids(int mode,int owner,int group) 345*4887Schin { 346*4887Schin if(mode & S_ISGID) 347*4887Schin setregid(rgroupid,group); 348*4887Schin 349*4887Schin /* set effective uid even if S_ISUID is not set. This is because 350*4887Schin * we are *really* executing EUID root at this point. Even if S_ISUID 351*4887Schin * is not set, the value for owner that is passsed should be correct. 352*4887Schin */ 353*4887Schin setreuid(ruserid,owner); 354*4887Schin } 355*4887Schin 356*4887Schin #else 357*4887Schin /* 358*4887Schin * This version of setids creats a /tmp file and copies itself into it. 359*4887Schin * The "clone" file is made executable with appropriate suid/sgid bits. 360*4887Schin * Finally, the clone is exec'ed. This file is unlinked by a grandchild 361*4887Schin * of this program, who waits around until the text is free. 362*4887Schin */ 363*4887Schin 364*4887Schin static void setids(int mode,uid_t owner,gid_t group) 365*4887Schin { 366*4887Schin register int n,m; 367*4887Schin int pv[2]; 368*4887Schin 369*4887Schin /* 370*4887Schin * Create a token to pass to the new program for validation. 371*4887Schin * This token can only be procured by someone running with an 372*4887Schin * effective userid of root, and hence gives the clone a way to 373*4887Schin * certify that it was really invoked by THISPROG. Someone who 374*4887Schin * is already root could spoof us, but why would they want to? 375*4887Schin * 376*4887Schin * Since we are root here, we must be careful: What if someone 377*4887Schin * linked a valuable file to tmpname? 378*4887Schin */ 379*4887Schin unlink(tmpname); /* should normally fail */ 380*4887Schin #ifdef O_EXCL 381*4887Schin if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 || 382*4887Schin unlink(tmpname) < 0) 383*4887Schin #else 384*4887Schin if((n = open(tmpname, O_WRONLY | O_CREAT ,SPECIAL)) < 0 || unlink(tmpname) < 0) 385*4887Schin #endif 386*4887Schin error_exit(badexec); 387*4887Schin if(n != FDVERIFY) 388*4887Schin { 389*4887Schin close(FDVERIFY); 390*4887Schin if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY) 391*4887Schin error_exit(badexec); 392*4887Schin } 393*4887Schin mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6); 394*4887Schin /* create a pipe for synchronization */ 395*4887Schin if(pipe(pv) < 0) 396*4887Schin error_exit(badexec); 397*4887Schin if((n=fork()) == 0) 398*4887Schin { /* child */ 399*4887Schin close(FDVERIFY); 400*4887Schin close(pv[1]); 401*4887Schin if((n=fork()) == 0) 402*4887Schin { /* grandchild -- cleans up clone file */ 403*4887Schin signal(SIGHUP, SIG_IGN); 404*4887Schin signal(SIGINT, SIG_IGN); 405*4887Schin signal(SIGQUIT, SIG_IGN); 406*4887Schin signal(SIGTERM, SIG_IGN); 407*4887Schin read(pv[0],pv,1); /* wait for clone to close pipe */ 408*4887Schin while(unlink(tmpname) < 0 && errno == ETXTBSY) 409*4887Schin sleep(1); 410*4887Schin exit(0); 411*4887Schin } 412*4887Schin else if(n == -1) 413*4887Schin exit(1); 414*4887Schin else 415*4887Schin { 416*4887Schin /* Create a set[ug]id file that will become the clone. 417*4887Schin * To make this atomic, without need for chown(), the 418*4887Schin * child takes on desired user and group. The only 419*4887Schin * downsize of this that I can see is that it may 420*4887Schin * screw up some per- * user accounting. 421*4887Schin */ 422*4887Schin if((m = open(THISPROG, O_RDONLY)) < 0) 423*4887Schin exit(1); 424*4887Schin if((mode & S_ISGID) && setgid(group) < 0) 425*4887Schin exit(1); 426*4887Schin if((mode & S_ISUID) && owner && setuid(owner) < 0) 427*4887Schin exit(1); 428*4887Schin #ifdef O_EXCL 429*4887Schin if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, mode)) < 0) 430*4887Schin #else 431*4887Schin unlink(tmpname); 432*4887Schin if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0) 433*4887Schin #endif /* O_EXCL */ 434*4887Schin exit(1); 435*4887Schin /* populate the clone */ 436*4887Schin m = mycopy(m,n); 437*4887Schin if(chmod(tmpname,mode) <0) 438*4887Schin exit(1); 439*4887Schin exit(m); 440*4887Schin } 441*4887Schin } 442*4887Schin else if(n == -1) 443*4887Schin error_exit(badexec); 444*4887Schin else 445*4887Schin { 446*4887Schin arglist[0] = (char*)tmpname; 447*4887Schin close(pv[0]); 448*4887Schin /* move write end of pipe into FDSYNC */ 449*4887Schin if(pv[1] != FDSYNC) 450*4887Schin { 451*4887Schin close(FDSYNC); 452*4887Schin if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC) 453*4887Schin error_exit(badexec); 454*4887Schin } 455*4887Schin /* wait for child to die */ 456*4887Schin while((m = wait(0)) != n) 457*4887Schin if(m == -1 && errno != EINTR) 458*4887Schin break; 459*4887Schin /* Kill any setuid status at this point. That way, if the 460*4887Schin * clone is not setuid, we won't exec it as root. Also, don't 461*4887Schin * neglect to consider that someone could have switched the 462*4887Schin * clone file on us. 463*4887Schin */ 464*4887Schin if(setuid(ruserid) < 0) 465*4887Schin error_exit(badexec); 466*4887Schin execv(tmpname,arglist); 467*4887Schin error_exit(badexec); 468*4887Schin } 469*4887Schin } 470*4887Schin 471*4887Schin /* 472*4887Schin * create a unique name into the <template> 473*4887Schin */ 474*4887Schin 475*4887Schin static void maketemp(char *template) 476*4887Schin { 477*4887Schin register char *cp = template; 478*4887Schin register pid_t n = getpid(); 479*4887Schin /* skip to end of string */ 480*4887Schin while(*++cp); 481*4887Schin /* convert process id to string */ 482*4887Schin while(n > 0) 483*4887Schin { 484*4887Schin *--cp = (n%10) + '0'; 485*4887Schin n /= 10; 486*4887Schin } 487*4887Schin 488*4887Schin } 489*4887Schin 490*4887Schin /* 491*4887Schin * copy THISPROG into the open file number <fdo> and close <fdo> 492*4887Schin */ 493*4887Schin 494*4887Schin static int mycopy(int fdi, int fdo) 495*4887Schin { 496*4887Schin char buffer[BLKSIZE]; 497*4887Schin register int n; 498*4887Schin 499*4887Schin while((n = read(fdi,buffer,BLKSIZE)) > 0) 500*4887Schin if(write(fdo,buffer,n) != n) 501*4887Schin break; 502*4887Schin close(fdi); 503*4887Schin close(fdo); 504*4887Schin return n; 505*4887Schin } 506*4887Schin 507*4887Schin #endif /* _lib_setreuid */ 508*4887Schin 509*4887Schin 510