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
main(argc,argv)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
endsh(shell)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
in_dir(dir,shell)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
error(message)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
eaccess(name,mode)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
setids(mode,owner,group)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
setids(mode,owner,group)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
mktemp(template)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
copy(fdo)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