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