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