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