1 /* 2 * This is a program to execute 'execute only' and suid/sgid shell scripts. 3 * This program must be owned by root and must have the suid bit set. 4 * This program must be installed as define parameter THISPROG for this to work 5 * on system V machines 6 * 7 * Written by David Korn 8 * AT&T Bell Laboratories 9 * ulysses!dgk 10 */ 11 12 /* The file name of the script to execute is argv[0] 13 * Argv[1] is the program name 14 * The basic idea is to open the script as standard input, set the effective 15 * user and group id correctly, and then exec the shell. 16 * The complicated part is getting the effective uid of the caller and 17 * setting the effective uid/gid. The program which execs this program 18 * must pass file descriptor FDIN as an open file with mode SPECIAL if 19 * the effective user id is not the real user id. The effective 20 * user id for authentication purposes will be the owner of this 21 * open file. On systems without the setreuid() call, e[ug]id is set 22 * by copying this program to a /tmp/file, making it a suid and/or sgid 23 * program, and then execing this program. 24 * A forked version of this program waits until it can unlink the /tmp 25 * file and then exits. Actually, we fork() twice so the parent can 26 * wait for the child to complete. A pipe is used to guarantee that we 27 * do not remove the /tmp file too soon. 28 */ 29 30 #ifdef BSD 31 # ifdef BSD_4_2 32 # include <fcntl.h> 33 # include <sys/param.h> 34 # else 35 # define fcntl(a,b,c) dup2(a,c) 36 # include <sys/types.h> 37 # endif /* BSD_4_2 */ 38 #else 39 # include <fcntl.h> 40 # include <sys/types.h> 41 #endif /* BSD */ 42 #include <sys/stat.h> 43 #include <errno.h> 44 45 #define SPECIAL 04100 /* setuid execute only by owner */ 46 #define FDIN 10 /* must be same as /dev/fd below */ 47 #define FDSYNC 11 /* used on sys5 to syncronize cleanup */ 48 #define BLKSIZE sizeof(char*)*1024 49 #define THISPROG "/etc/suid_exec" 50 #define DEFSHELL "/bin/sh" 51 52 extern char *getenv(); 53 extern int errno; 54 55 static int error(); 56 static int in_dir(); 57 static int endsh(); 58 #ifndef BSD_4_2 59 static int copy(); 60 static void mktemp(); 61 #endif /* BSD_4_2 */ 62 63 static char version[] = "@(#)suid_exec 06/03/86a"; 64 static char badopen[] = "cannot open"; 65 static char badexec[] = "cannot exec"; 66 static char tmpname[] = "/tmp/SUIDXXXXXX"; 67 static char devfd[] = "/dev/fd/10"; 68 static char **arglist; 69 70 static char *shell; 71 static char *command; 72 static int created; 73 static int ruserid; 74 static int euserid; 75 static int rgroupid; 76 static int egroupid; 77 static struct stat statb; 78 79 main(argc,argv) 80 char *argv[]; 81 { 82 register int n; 83 register char *p; 84 int mode; 85 int effuid; 86 int effgid; 87 int priv = 0; 88 arglist = argv; 89 command = argv[1]; 90 ruserid = getuid(); 91 euserid = geteuid(); 92 rgroupid = getgid(); 93 egroupid = getegid(); 94 p = argv[0]; 95 #ifndef BSD_4_2 96 mktemp(tmpname); 97 if(strcmp(p,tmpname)==0) 98 { 99 /* This enables the grandchild to clean up /tmp file */ 100 close(FDSYNC); 101 /* make sure that this is a valid invocation of /tmp prog */ 102 /* the /tmp file must be owned by root, setuid, and not a link */ 103 if(euserid==0 && ruserid!=0) 104 { 105 /* keep out forgers */ 106 if(stat(tmpname,&statb) < 0) 107 error(badexec); 108 /* don't trust /tmp file unless suid root */ 109 if((statb.st_mode&S_ISUID)==0 || statb.st_uid 110 || statb.st_nlink!=1) 111 error(badexec); 112 } 113 goto exec; 114 } 115 /* make sure that this is the real setuid program, not the clone */ 116 if(euserid || command==(char*)0) 117 error(badexec); 118 #endif /* BSD_4_2 */ 119 /* validate execution rights to this script */ 120 if(fstat(FDIN,&statb)<0) 121 { 122 effuid++; 123 euserid = ruserid; 124 } 125 else 126 { 127 priv++; 128 if((statb.st_mode&~S_IFMT) != SPECIAL) 129 error(badexec); 130 euserid = statb.st_uid; 131 } 132 /* do it the easy way if you can */ 133 if(euserid == ruserid && egroupid==rgroupid) 134 { 135 if(access(p,1) < 0) 136 error(badexec); 137 } 138 else 139 { 140 /* have to check access on each component */ 141 while(*p++) 142 { 143 if(*p == '/' || *p==0) 144 { 145 n = *p; 146 *p = 0; 147 if(eaccess(argv[0],1) < 0) 148 error(badexec); 149 *p = n; 150 } 151 } 152 p = argv[0]; 153 } 154 /* open the script for reading and make it be FDIN */ 155 n = open(p,0); 156 if(n < 0) 157 error(badopen); 158 if(fstat(n,&statb)<0) 159 error(badopen); 160 close(FDIN); 161 if(fcntl(n,F_DUPFD,FDIN)!=FDIN) 162 error(badexec); 163 close(n); 164 /* compute the desired new effective user and group id */ 165 effuid = euserid; 166 effgid = egroupid; 167 mode = 0; 168 if(priv && statb.st_mode & S_ISUID) 169 effuid = statb.st_uid; 170 if(priv && statb.st_mode & S_ISGID) 171 effgid = statb.st_gid; 172 /* see if group needs setting */ 173 if(effgid != egroupid) 174 if(effgid != rgroupid || setgid(rgroupid)<0) 175 mode = S_ISGID; 176 177 /* now see if the uid needs setting */ 178 if(effuid) 179 if(mode || effuid != ruserid || setuid(ruserid)<0) 180 mode |= S_ISUID; 181 if(mode) 182 setids(mode,effuid,effgid); 183 exec: 184 /* only use SHELL if file is in trusted directory and ends in sh */ 185 shell = getenv("SHELL"); 186 if(shell==0 || !endsh(shell) || ( 187 !in_dir("/bin",shell) && 188 !in_dir("/usr/bin",shell) && 189 !in_dir("/usr/lbin",shell))) 190 shell = DEFSHELL; 191 argv[0] = command; 192 argv[1] = devfd; 193 execv(shell,argv); 194 error(badexec); 195 } 196 197 /* 198 * return true of shell ends in sh 199 */ 200 201 static int endsh(shell) 202 register char *shell; 203 { 204 while(*shell) 205 shell++; 206 if(*--shell != 'h' || *--shell != 's') 207 return(0); 208 return(1); 209 } 210 211 212 /* 213 * return true of shell is in <dir> directory 214 */ 215 216 static int in_dir(dir,shell) 217 register char *dir; 218 register char *shell; 219 { 220 while(*dir) 221 { 222 if(*dir++ != *shell++) 223 return(0); 224 } 225 /* return true if next character is a '/' */ 226 return(*shell=='/'); 227 } 228 229 static int error(message) 230 { 231 printf("%s: %s\n",command,message); 232 if(created) 233 unlink(tmpname); 234 exit(1); 235 } 236 237 238 /* 239 * This version of access checks against effective uid and effective gid 240 */ 241 242 eaccess(name, mode) 243 register char *name; 244 register int mode; 245 { 246 struct stat statb; 247 if (stat(name, &statb) == 0) 248 { 249 if(euserid == 0) 250 { 251 if((statb.st_mode&S_IFMT) != S_IFREG || mode != 1) 252 return(0); 253 /* root needs execute permission for someone */ 254 mode = (S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6)); 255 } 256 else if(euserid == statb.st_uid) 257 mode <<= 6; 258 else if(egroupid == statb.st_gid) 259 mode <<= 3; 260 #ifdef BSD 261 # ifdef BSD_4_2 262 /* in BSD_4_2 you can be in several groups */ 263 else 264 { 265 int groups[NGROUPS]; 266 register int n; 267 n = getgroups(NGROUPS,groups); 268 while(--n >= 0) 269 { 270 if(groups[n] == statb.st_gid) 271 { 272 mode <<= 3; 273 break; 274 } 275 } 276 } 277 # endif /* BSD_4_2 */ 278 #endif /* BSD */ 279 if(statb.st_mode & mode) 280 return(0); 281 } 282 return(-1); 283 } 284 285 #ifdef BSD_4_2 286 setids(mode,owner,group) 287 { 288 if(mode & S_ISGID) 289 setregid(rgroupid,group); 290 if(mode & S_ISUID) 291 setreuid(ruserid,owner); 292 } 293 294 #else 295 /* 296 * This version of setids creats a /tmp file and copies this program into 297 * it. The /tmp file is made executable with appropriate suid/sgid bits. 298 * Finally, the /tmp file is exec'ed. The /tmp file is unlinked by a 299 * grandchild of this program, who waits until the text is free 300 */ 301 302 setids(mode,owner,group) 303 { 304 register int n; 305 int pv[2]; 306 /* create a setuid program with a copy of this program without RW */ 307 if((n=creat(tmpname,mode|SPECIAL)) < 0) 308 error(badexec); 309 created++; 310 if(chown(tmpname,owner,group) < 0) 311 error(badexec); 312 copy(n); 313 /* create a pipe for syncronization */ 314 pv[0] = pv[1] = -1; 315 pipe(pv); 316 /* pipe failure could cause the /tmp file to be removed prematurely */ 317 /* This is not worth waiting for */ 318 if((n=fork())==0) 319 { 320 char buf[2]; 321 if(fork()) 322 exit(0); 323 /* the grandchild has to clean up the text file */ 324 close(pv[1]); 325 /* wait until the parent closes the pipe */ 326 read(pv[0],buf,1); 327 while(1) 328 { 329 /* sleep granularity too crude to trust sleep(1) */ 330 sleep(1); 331 if(unlink(tmpname) >= 0) 332 exit(0); 333 else if(errno != ETXTBSY) 334 exit(1); 335 } 336 } 337 else if(n == -1) 338 error(badexec); 339 else 340 { 341 arglist[0] = tmpname; 342 close(pv[0]); 343 while(wait(0)!= -1); 344 /* put write end of pipe into FDSYNC */ 345 close(FDSYNC); 346 if(pv[1] != FDSYNC) 347 n = fcntl(pv[1],F_DUPFD,FDSYNC); 348 if(n != FDSYNC) 349 close(n); 350 close(pv[1]); 351 execv(tmpname,arglist); 352 error(badexec); 353 } 354 } 355 356 /* 357 * create a unique name into the <template> 358 */ 359 360 static void mktemp(template) 361 char *template; 362 { 363 register char *cp = template; 364 register int n = getpid(); 365 /* skip to end of string */ 366 while(*++cp); 367 /* convert process id to string */ 368 while(n > 0) 369 { 370 *--cp = (n%10) + '0'; 371 n /= 10; 372 } 373 374 } 375 376 /* 377 * copy THISPROG into the open file number <fdo> and close <fdo> 378 */ 379 380 static int copy(fdo) 381 int fdo; 382 { 383 char buffer[BLKSIZE]; 384 register int n; 385 int fdi; 386 if((fdi = open(THISPROG,0)) < 0) 387 error(badexec); 388 while((n = read(fdi,buffer,BLKSIZE))!=0) 389 { 390 if(n < 0) 391 error(badexec); 392 write(fdo,buffer,n); 393 } 394 close(fdi); 395 return(close(fdo)); 396 } 397 398 #endif /* BSD_4_2 */ 399