4d6ddab231810caee1f2361c43b21780bcb807b9
[oweals/cde.git] / cde / programs / dtksh / ksh93 / src / cmd / ksh93 / sh / suid_exec.c
1 /* $XConsortium: suid_exec.c /main/4 1995/12/12 15:09:04 rswiston $ */
2 /***************************************************************
3 *                                                              *
4 *                      AT&T - PROPRIETARY                      *
5 *                                                              *
6 *        THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF        *
7 *                    AT&T BELL LABORATORIES                    *
8 *         AND IS NOT TO BE DISCLOSED OR USED EXCEPT IN         *
9 *            ACCORDANCE WITH APPLICABLE AGREEMENTS             *
10 *                                                              *
11 *                Copyright (c) 1995 AT&T Corp.                 *
12 *              Unpublished & Not for Publication               *
13 *                     All Rights Reserved                      *
14 *                                                              *
15 *       The copyright notice above does not evidence any       *
16 *      actual or intended publication of such source code      *
17 *                                                              *
18 *               This software was created by the               *
19 *           Advanced Software Technology Department            *
20 *                    AT&T Bell Laboratories                    *
21 *                                                              *
22 *               For further information contact                *
23 *                    {research,attmail}!dgk                    *
24 *                                                              *
25 ***************************************************************/
26
27 /* : : generated by proto : : */
28
29 #if !defined(__PROTO__)
30 #if defined(__STDC__) || defined(__cplusplus) || defined(_proto) || defined(c_plusplus)
31 #if defined(__cplusplus)
32 #define __MANGLE__      "C"
33 #else
34 #define __MANGLE__
35 #endif
36 #define __STDARG__
37 #define __PROTO__(x)    x
38 #define __OTORP__(x)
39 #define __PARAM__(n,o)  n
40 #if !defined(__STDC__) && !defined(__cplusplus)
41 #if !defined(c_plusplus)
42 #define const
43 #endif
44 #define signed
45 #define void            int
46 #define volatile
47 #define __V_            char
48 #else
49 #define __V_            void
50 #endif
51 #else
52 #define __PROTO__(x)    ()
53 #define __OTORP__(x)    x
54 #define __PARAM__(n,o)  o
55 #define __MANGLE__
56 #define __V_            char
57 #define const
58 #define signed
59 #define void            int
60 #define volatile
61 #endif
62 #if defined(__cplusplus) || defined(c_plusplus)
63 #define __VARARG__      ...
64 #else
65 #define __VARARG__
66 #endif
67 #if defined(__STDARG__)
68 #define __VA_START__(p,a)       va_start(p,a)
69 #else
70 #define __VA_START__(p,a)       va_start(p)
71 #endif
72 #endif
73 #include        <ast.h>
74 #include        "FEATURE/externs"
75 #include        <ls.h>
76 #include        <sig.h>
77 #include        <error.h>
78
79 #define SPECIAL         04100   /* setuid execute only by owner */
80 #define FDIN            10      /* must be same as /dev/fd below */
81 #define FDSYNC          11      /* used on sys5 to synchronize cleanup */
82 #define FDVERIFY        12      /* used to validate /tmp process */
83 #define BLKSIZE         sizeof(char*)*1024
84 #define THISPROG        SUIDEXECPATH
85 #define DEFSHELL        "/bin/sh"
86
87 static void error_exit __PROTO__((const char*));
88 static int in_dir __PROTO__((const char*, const char*));
89 static int endsh __PROTO__((const char*));
90 #ifndef _lib_setreuid
91     static int mycopy __PROTO__((int, int));
92     static void maketemp __PROTO__((char*));
93 #endif /* _lib_setreuid */
94
95 static const char version[]     = "\n@(#)suid_exec 12/28/93\n";
96 static const char badopen[]     = "cannot open";
97 static const char badexec[]     = "cannot exec";
98 static const char devfd[]       = "/dev/fd/10"; /* must match FDIN above */
99 static char tmpname[]           = "/tmp/SUIDXXXXXX";
100 static char **arglist;
101
102 static char *shell;
103 static char *command;
104 static uid_t ruserid;
105 static uid_t euserid;
106 static gid_t rgroupid;
107 static gid_t egroupid;
108 static struct stat statb;
109
110 main __PARAM__((int argc,char *argv[]), (argc, argv)) __OTORP__(int argc;char *argv[];){
111         register int m,n;
112         register char *p;
113         struct stat statx;
114         int mode;
115         uid_t effuid;
116         gid_t effgid;
117         NOT_USED(argc);
118         arglist = argv;
119         if((command = argv[1]) == 0)
120                 error_exit(badexec);
121         ruserid = getuid();
122         euserid = geteuid();
123         rgroupid = getgid();
124         egroupid = getegid();
125         p = argv[0];
126 #ifndef _lib_setreuid
127         maketemp(tmpname);
128         if(strcmp(p,tmpname)==0)
129         {
130                 /* At this point, the presumption is that we are the
131                  * version of THISPROG copied into /tmp, with the owner,
132                  * group, and setuid/gid bits correctly set.  This copy of
133                  * the program is executable by anyone, so we must be careful
134                  * not to allow just any invocation of it to succeed, since
135                  * it is setuid/gid.  Validate the proper execution by
136                  * examining the FDVERIFY file descriptor -- if it is owned
137                  * by root and is mode SPECIAL, then this is proof that it was
138                  * passed by a program with superuser privileges -- hence we
139                  * can presume legitimacy.  Otherwise, bail out, as we suspect
140                  * an impostor.
141                  */
142                 if(fstat(FDVERIFY,&statb) < 0 || statb.st_uid != 0 ||
143                     (statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0)
144                         error_exit(badexec);
145                 /* This enables the grandchild to clean up /tmp file */
146                 close(FDSYNC);
147                 /* Make sure that this is a valid invocation of the clone.
148                  * Perhaps unnecessary, given FDVERIFY, but what the heck...
149                  */
150                 if(stat(tmpname,&statb) < 0 || statb.st_nlink != 1 ||
151                     !S_ISREG(statb.st_mode))
152                         error_exit(badexec);
153                 if(ruserid != euserid &&
154                   ((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid))
155                         error_exit(badexec);
156                 goto exec;
157         }
158         /* Make sure that this is the real setuid program, not the clone.
159          * It is possible by clever hacking to get past this point in the
160          * clone, but it doesn't do the hacker any good that I can see.
161          */
162         if(euserid)
163                 error_exit(badexec);
164 #endif /* _lib_setreuid */
165         /* Open the script for reading first and then validate it.  This
166          * prevents someone from pulling a switcheroo while we are validating.
167          */
168         n = open(p,0);
169         if(n == FDIN)
170         {
171                 n = dup(n);
172                 close(FDIN);
173         }
174         if(n < 0)
175                 error_exit(badopen);
176         /* validate execution rights to this script */
177         if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL)
178                 euserid = ruserid;
179         else
180                 euserid = statb.st_uid;
181         /* do it the easy way if you can */
182         if(euserid == ruserid && egroupid == rgroupid)
183         {
184                 if(access(p,X_OK) < 0)
185                         error_exit(badexec);
186         }
187         else
188         {
189                 /* have to check access on each component */
190                 while(*p++)
191                 {
192                         if(*p == '/' || *p == 0)
193                         {
194                                 m = *p;
195                                 *p = 0;
196                                 if(eaccess(argv[0],X_OK) < 0)
197                                         error_exit(badexec);
198                                 *p = m;
199                         }
200                 }
201                 p = argv[0];
202         }
203         if(fstat(n, &statb) < 0 || !S_ISREG(statb.st_mode))
204                 error_exit(badopen);
205         if(stat(p, &statx) < 0 ||
206           statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev)
207                 error_exit(badexec);
208         if(stat(THISPROG, &statx) < 0 ||
209           (statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev))
210                 error_exit(badexec);
211         close(FDIN);
212         if(fcntl(n,F_DUPFD,FDIN) != FDIN)
213                 error_exit(badexec);
214         close(n);
215
216         /* compute the desired new effective user and group id */
217         effuid = euserid;
218         effgid = egroupid;
219         mode = 0;
220         if(statb.st_mode & S_ISUID)
221                 effuid = statb.st_uid;
222         if(statb.st_mode & S_ISGID)
223                 effgid = statb.st_gid;
224
225         /* see if group needs setting */
226         if(effgid != egroupid)
227                 if(effgid != rgroupid || setgid(rgroupid) < 0)
228                         mode = S_ISGID;
229                 
230         /* now see if the uid needs setting */
231         if(mode)
232         {
233                 if(effuid != ruserid)
234                         mode |= S_ISUID;
235         }
236         else if(effuid)
237         {
238                 if(effuid != ruserid || setuid(ruserid) < 0)
239                         mode = S_ISUID;
240         }
241                 
242         if(mode)
243                 setids(mode, effuid, effgid);
244 exec:
245         /* only use SHELL if file is in trusted directory and ends in sh */
246         shell = getenv("SHELL");
247         if(shell == 0 || !endsh(shell) || (
248                 !in_dir(CDE_INSTALLATION_TOP"/bin",shell) &&
249                 !in_dir("/bin",shell) &&
250                 !in_dir("/usr/bin",shell) &&
251                 !in_dir("/usr/lbin",shell) &&
252                 !in_dir("/usr/local/bin",shell)))
253                         shell = DEFSHELL;
254         argv[0] = command;
255         argv[1] = (char*)devfd;
256         execv(shell,argv);
257         error_exit(badexec);
258 }
259
260 /*
261  * return true of shell ends in sh
262  */
263
264 static int endsh __PARAM__((register const char *shell), (shell)) __OTORP__(register const char *shell;){
265         while(*shell)
266                 shell++;
267         if(*--shell != 'h' || *--shell != 's')
268                 return(0);
269         return(1);
270 }
271
272
273 /*
274  * return true of shell is in <dir> directory
275  */
276
277 static int in_dir __PARAM__((register const char *dir,register const char *shell), (dir, shell)) __OTORP__(register const char *dir;register const char *shell;){
278         while(*dir)
279         {
280                 if(*dir++ != *shell++)
281                         return(0);
282         }
283         /* return true if next character is a '/' */
284         return(*shell=='/');
285 }
286
287 static void error_exit __PARAM__((const char *message), (message)) __OTORP__(const char *message;){
288         sfprintf(sfstdout,"%s: %s\n",command,message);
289         exit(126);
290 }
291
292
293 /*
294  * This version of access checks against effective uid and effective gid
295  */
296
297 eaccess __PARAM__((register const char *name, register int mode), (name, mode)) __OTORP__(register const char *name; register int mode;){       
298         struct stat statb;
299         if (stat(name, &statb) == 0)
300         {
301                 if(euserid == 0)
302                 {
303                         if(!S_ISREG(statb.st_mode) || mode != 1)
304                                 return(0);
305                         /* root needs execute permission for someone */
306                         mode = (S_IXUSR|S_IXGRP|S_IXOTH);
307                 }
308                 else if(euserid == statb.st_uid)
309                         mode <<= 6;
310                 else if(egroupid == statb.st_gid)
311                         mode <<= 3;
312 #ifdef _lib_getgroups
313                 /* on some systems you can be in several groups */
314                 else
315                 {
316                         static int maxgroups = 0;
317                         gid_t *groups; 
318                         register int n;
319                         if(maxgroups==0)
320                         {
321                                 /* first time */
322                                 if((maxgroups=getgroups(0, (gid_t *)NULL)) <= 0)
323                                 {
324                                         /* pre-POSIX system */
325                                         maxgroups=NGROUPS_MAX;
326                                 }
327                         }
328                         groups = (gid_t*)malloc((maxgroups+1)*sizeof(gid_t));
329                         n = getgroups(maxgroups,groups);
330                         while(--n >= 0)
331                         {
332                                 if(groups[n] == statb.st_gid)
333                                 {
334                                         mode <<= 3;
335                                         break;
336                                 }
337                         }
338                         free((void *)groups);
339                 }
340 #endif /* _lib_getgroups */
341                 if(statb.st_mode & mode)
342                         return(0);
343         }
344         return(-1);
345 }
346
347 #ifdef _lib_setreuid
348 setids __PARAM__((int mode,int owner,int group), (mode, owner, group)) __OTORP__(int mode;int owner;int group;){
349         if(mode & S_ISGID)
350                 setregid(rgroupid,group);
351
352         /* set effective uid even if S_ISUID is not set.  This is because
353          * we are *really* executing EUID root at this point.  Even if S_ISUID
354          * is not set, the value for owner that is passsed should be correct.
355          */
356         setreuid(ruserid,owner);
357 }
358
359 #else
360 /*
361  * This version of setids creats a /tmp file and copies itself into it.
362  * The "clone" file is made executable with appropriate suid/sgid bits.
363  * Finally, the clone is exec'ed.  This file is unlinked by a grandchild
364  * of this program, who waits around until the text is free.
365  */
366
367 setids __PARAM__((int mode,uid_t owner,gid_t group), (mode, owner, group)) __OTORP__(int mode;uid_t owner;gid_t group;){
368         register int n,m;
369         int pv[2];
370
371         /*
372          * Create a token to pass to the new program for validation.
373          * This token can only be procured by someone running with an
374          * effective userid of root, and hence gives the clone a way to
375          * certify that it was really invoked by THISPROG.  Someone who
376          * is already root could spoof us, but why would they want to?
377          *
378          * Since we are root here, we must be careful:  What if someone
379          * linked a valuable file to tmpname?
380          */
381         unlink(tmpname);        /* should normally fail */
382 #ifdef O_EXCL
383         if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 ||
384                 unlink(tmpname) < 0)
385 #else
386         if((n = creat(tmpname, SPECIAL)) < 0 || unlink(tmpname) < 0)
387 #endif
388                 error_exit(badexec);
389         if(n != FDVERIFY)
390         {
391                 close(FDVERIFY);
392                 if(fcntl(n,F_DUPFD,FDVERIFY) != FDVERIFY)
393                         error_exit(badexec);
394         }
395         mode |= S_IEXEC|(S_IEXEC>>3)|(S_IEXEC>>6);
396         /* create a pipe for synchronization */
397         if(pipe(pv) < 0)
398                 error_exit(badexec);
399         if((n=fork()) == 0)
400         {       /* child */
401                 close(FDVERIFY);
402                 close(pv[1]);
403                 if((n=fork()) == 0)
404                 {       /* grandchild -- cleans up clone file */
405                         signal(SIGHUP, SIG_IGN);
406                         signal(SIGINT, SIG_IGN);
407                         signal(SIGQUIT, SIG_IGN);
408                         signal(SIGTERM, SIG_IGN);
409                         read(pv[0],pv,1); /* wait for clone to close pipe */
410                         while(unlink(tmpname) < 0 && errno == ETXTBSY)
411                                 sleep(1);
412                         exit(0);
413                 }
414                 else if(n == -1)
415                         exit(1);
416                 else
417                 {
418                         /* Create a set[ug]id file that will become the clone. 
419                          * To make this atomic, without need for chown(), the
420                          * child takes on desired user and group.  The only
421                          * downsize of this that I can see is that it may
422                          * screw up some per- * user accounting.
423                          */
424                         if((m = open(THISPROG, O_RDONLY)) < 0)
425                                 exit(1);
426                         if((mode & S_ISGID) && setgid(group) < 0)
427                                 exit(1);
428                         if((mode & S_ISUID) && owner && setuid(owner) < 0)
429                                 exit(1);
430 #ifdef O_EXCL
431                         if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, mode)) < 0)
432 #else
433                         unlink(tmpname);
434                         if((n = creat(tmpname, mode)) < 0)
435 #endif /* O_EXCL */
436                                 exit(1);
437                         /* populate the clone */
438                         m = mycopy(m,n);
439                         if(chmod(tmpname,mode) <0)
440                                 exit(1);
441                         exit(m);
442                 }
443         }
444         else if(n == -1)
445                 error_exit(badexec);
446         else
447         {
448                 arglist[0] = (char*)tmpname;
449                 close(pv[0]);
450                 /* move write end of pipe into FDSYNC */
451                 if(pv[1] != FDSYNC)
452                 {
453                         close(FDSYNC);
454                         if(fcntl(pv[1],F_DUPFD,FDSYNC) != FDSYNC)
455                                 error_exit(badexec);
456                 }
457                 /* wait for child to die */
458                 while((m = wait(0)) != n)
459                         if(m == -1 && errno != EINTR)
460                                 break;
461                 /* Kill any setuid status at this point.  That way, if the
462                  * clone is not setuid, we won't exec it as root.  Also, don't
463                  * neglect to consider that someone could have switched the
464                  * clone file on us.
465                  */
466                 if(setuid(ruserid) < 0)
467                         error_exit(badexec);
468                 execv(tmpname,arglist);
469                 error_exit(badexec);
470         }
471 }
472
473 /*
474  * create a unique name into the <template>
475  */
476
477 static void maketemp __PARAM__((char *template), (template)) __OTORP__(char *template;){
478         register char *cp = template;
479         register pid_t n = getpid();
480         /* skip to end of string */
481         while(*++cp);
482         /* convert process id to string */
483         while(n > 0)
484         {
485                 *--cp = (n%10) + '0';
486                 n /= 10;
487         }
488         
489 }
490
491 /*
492  *  copy THISPROG into the open file number <fdo> and close <fdo>
493  */
494
495 static int mycopy __PARAM__((int fdi, int fdo), (fdi, fdo)) __OTORP__(int fdi; int fdo;){
496         char buffer[BLKSIZE];
497         register int n;
498
499         while((n = read(fdi,buffer,BLKSIZE)) > 0)
500                 if(write(fdo,buffer,n) != n)
501                         break;
502         close(fdi);
503         close(fdo);
504         return n;
505 }
506
507 #endif /* _lib_setreuid */
508
509