6185db853e024a486ff8837e6784dd290d866112dougm/***********************************************************************
6185db853e024a486ff8837e6784dd290d866112dougm* This software is part of the ast package *
6185db853e024a486ff8837e6784dd290d866112dougm* Copyright (c) 1982-2010 AT&T Intellectual Property *
6185db853e024a486ff8837e6784dd290d866112dougm* and is licensed under the *
6185db853e024a486ff8837e6784dd290d866112dougm* Common Public License, Version 1.0 *
6185db853e024a486ff8837e6784dd290d866112dougm* by AT&T Intellectual Property *
6185db853e024a486ff8837e6784dd290d866112dougm* A copy of the License is available at *
6185db853e024a486ff8837e6784dd290d866112dougm* (with md5 checksum 059e8cd6165cb4c31e351f2b69388fd9) *
6185db853e024a486ff8837e6784dd290d866112dougm* Information and Software Systems Research *
6185db853e024a486ff8837e6784dd290d866112dougm* AT&T Research *
6185db853e024a486ff8837e6784dd290d866112dougm* Florham Park NJ *
6185db853e024a486ff8837e6784dd290d866112dougm* David Korn <dgk@research.att.com> *
6185db853e024a486ff8837e6784dd290d866112dougm***********************************************************************/
6185db853e024a486ff8837e6784dd290d866112dougm * This is a program to execute 'execute only' and suid/sgid shell scripts.
dc20a3024900c47dd2ee44b9707e6df38f7d62a5as * This program must be owned by root and must have the set uid bit set.
6185db853e024a486ff8837e6784dd290d866112dougm * It must not have the set group id bit set. This program must be installed
6185db853e024a486ff8837e6784dd290d866112dougm * where the define parameter THISPROG indicates to work correctly on system V
6185db853e024a486ff8837e6784dd290d866112dougm * Written by David Korn
6185db853e024a486ff8837e6784dd290d866112dougm * AT&T Labs
6185db853e024a486ff8837e6784dd290d866112dougm * Enhanced by Rob Stampfli
6185db853e024a486ff8837e6784dd290d866112dougm/* The file name of the script to execute is argv[0]
6185db853e024a486ff8837e6784dd290d866112dougm * Argv[1] is the program name
6185db853e024a486ff8837e6784dd290d866112dougm * The basic idea is to open the script as standard input, set the effective
6185db853e024a486ff8837e6784dd290d866112dougm * user and group id correctly, and then exec the shell.
6185db853e024a486ff8837e6784dd290d866112dougm * The complicated part is getting the effective uid of the caller and
6185db853e024a486ff8837e6784dd290d866112dougm * setting the effective uid/gid. The program which execs this program
6185db853e024a486ff8837e6784dd290d866112dougm * may pass file descriptor FDIN as an open file with mode SPECIAL if
6185db853e024a486ff8837e6784dd290d866112dougm * the effective user id is not the real user id. The effective
6185db853e024a486ff8837e6784dd290d866112dougm * user id for authentication purposes will be the owner of this
6185db853e024a486ff8837e6784dd290d866112dougm * open file. On systems without the setreuid() call, e[ug]id is set
6185db853e024a486ff8837e6784dd290d866112dougm * by copying this program to a /tmp/file, making it a suid and/or sgid
6185db853e024a486ff8837e6784dd290d866112dougm * program, and then execing this program.
6185db853e024a486ff8837e6784dd290d866112dougm * A forked version of this program waits until it can unlink the /tmp
6185db853e024a486ff8837e6784dd290d866112dougm * file and then exits. Actually, we fork() twice so the parent can
6185db853e024a486ff8837e6784dd290d866112dougm * wait for the child to complete. A pipe is used to guarantee that we
6185db853e024a486ff8837e6784dd290d866112dougm * do not remove the /tmp file too soon.
6185db853e024a486ff8837e6784dd290d866112dougm#define SPECIAL 04100 /* setuid execute only by owner */
6185db853e024a486ff8837e6784dd290d866112dougm#define FDSYNC 11 /* used on sys5 to synchronize cleanup */
6185db853e024a486ff8837e6784dd290d866112dougm#define FDVERIFY 12 /* used to validate /tmp process */
6185db853e024a486ff8837e6784dd290d866112dougmstatic void error_exit(const char*);
6185db853e024a486ff8837e6784dd290d866112dougmstatic int in_dir(const char*, const char*);
6185db853e024a486ff8837e6784dd290d866112dougmstatic int endsh(const char*);
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm static int mycopy(int, int);
6185db853e024a486ff8837e6784dd290d866112dougm static void maketemp(char*);
6185db853e024a486ff8837e6784dd290d866112dougm static void setids(int,int,int);
6185db853e024a486ff8837e6784dd290d866112dougm#endif /* _lib_setreuid */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwstatic const char version[] = "\n@(#)$Id: suid_exec "SH_RELEASE" $\n";
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwstatic const char devfd[] = "/dev/fd/10"; /* must match FDIN above */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw register int m,n;
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw register char *p;
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* At this point, the presumption is that we are the
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * version of THISPROG copied into /tmp, with the owner,
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * group, and setuid/gid bits correctly set. This copy of
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * the program is executable by anyone, so we must be careful
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * not to allow just any invocation of it to succeed, since
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * it is setuid/gid. Validate the proper execution by
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * examining the FDVERIFY file descriptor -- if it is owned
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * by root and is mode SPECIAL, then this is proof that it was
55bf511df53aad0fdb7eb3fa349f0308cc05234cas * passed by a program with superuser privileges -- hence we
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * can presume legitimacy. Otherwise, bail out, as we suspect
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * an impostor.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw (statb.st_mode & ~S_IFMT) != SPECIAL || close(FDVERIFY)<0)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* This enables the grandchild to clean up /tmp file */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* Make sure that this is a valid invocation of the clone.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * Perhaps unnecessary, given FDVERIFY, but what the heck...
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw ((statb.st_mode & S_ISUID) == 0 || statb.st_uid != euserid))
55bf511df53aad0fdb7eb3fa349f0308cc05234cas /* Make sure that this is the real setuid program, not the clone.
55bf511df53aad0fdb7eb3fa349f0308cc05234cas * It is possible by clever hacking to get past this point in the
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * clone, but it doesn't do the hacker any good that I can see.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw#endif /* _lib_setreuid */
55bf511df53aad0fdb7eb3fa349f0308cc05234cas /* Open the script for reading first and then validate it. This
55bf511df53aad0fdb7eb3fa349f0308cc05234cas * prevents someone from pulling a switcheroo while we are validating.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* validate execution rights to this script */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if(fstat(FDIN,&statb) < 0 || (statb.st_mode & ~S_IFMT) != SPECIAL)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* do it the easy way if you can */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* have to check access on each component */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw while(*p++)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw if(*p == '/' || *p == 0)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw statb.st_ino != statx.st_ino || statb.st_dev != statx.st_dev)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw (statb.st_ino == statx.st_ino && statb.st_dev == statx.st_dev))
55bf511df53aad0fdb7eb3fa349f0308cc05234cas /* compute the desired new effective user and group id */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* see if group needs setting */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* now see if the uid needs setting */
573b0c00a1ee520c3f6938dda8d693236f37ae60dougm#endif /* _lib_setreuid */
573b0c00a1ee520c3f6938dda8d693236f37ae60dougm /* only use SHELL if file is in trusted directory and ends in sh */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * return true of shell ends in sh of ksh
573b0c00a1ee520c3f6938dda8d693236f37ae60dougm * return true of shell is in <dir> directory
573b0c00a1ee520c3f6938dda8d693236f37ae60dougmstatic int in_dir(register const char *dir,register const char *shell)
573b0c00a1ee520c3f6938dda8d693236f37ae60dougm /* return true if next character is a '/' */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * This version of access checks against effective uid and effective gid
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amwint eaccess(register const char *name, register int mode)
6185db853e024a486ff8837e6784dd290d866112dougm /* root needs execute permission for someone */
6185db853e024a486ff8837e6784dd290d866112dougm /* on some systems you can be in several groups */
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm register int n;
6185db853e024a486ff8837e6784dd290d866112dougm /* first time */
6185db853e024a486ff8837e6784dd290d866112dougm /* pre-POSIX system */
6185db853e024a486ff8837e6784dd290d866112dougm while(--n >= 0)
6185db853e024a486ff8837e6784dd290d866112dougm#endif /* _lib_getgroups */
6185db853e024a486ff8837e6784dd290d866112dougm return(-1);
6185db853e024a486ff8837e6784dd290d866112dougm /* set effective uid even if S_ISUID is not set. This is because
6185db853e024a486ff8837e6784dd290d866112dougm * we are *really* executing EUID root at this point. Even if S_ISUID
6185db853e024a486ff8837e6784dd290d866112dougm * is not set, the value for owner that is passsed should be correct.
6185db853e024a486ff8837e6784dd290d866112dougm * This version of setids creats a /tmp file and copies itself into it.
6185db853e024a486ff8837e6784dd290d866112dougm * The "clone" file is made executable with appropriate suid/sgid bits.
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * Finally, the clone is exec'ed. This file is unlinked by a grandchild
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * of this program, who waits around until the text is free.
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm register int n,m;
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * Create a token to pass to the new program for validation.
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * This token can only be procured by someone running with an
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * effective userid of root, and hence gives the clone a way to
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * certify that it was really invoked by THISPROG. Someone who
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * is already root could spoof us, but why would they want to?
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * Since we are root here, we must be careful: What if someone
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm * linked a valuable file to tmpname?
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm if((n = open(tmpname, O_WRONLY | O_CREAT | O_EXCL, SPECIAL)) < 0 ||
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm if((n = open(tmpname, O_WRONLY | O_CREAT ,SPECIAL)) < 0 || unlink(tmpname) < 0)
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm /* create a pipe for synchronization */
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm if((n=fork()) == 0)
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm { /* child */
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm if((n=fork()) == 0)
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm { /* grandchild -- cleans up clone file */
6185db853e024a486ff8837e6784dd290d866112dougm else if(n == -1)
6185db853e024a486ff8837e6784dd290d866112dougm /* Create a set[ug]id file that will become the clone.
6185db853e024a486ff8837e6784dd290d866112dougm * To make this atomic, without need for chown(), the
6185db853e024a486ff8837e6784dd290d866112dougm * child takes on desired user and group. The only
6185db853e024a486ff8837e6784dd290d866112dougm * downsize of this that I can see is that it may
6185db853e024a486ff8837e6784dd290d866112dougm * screw up some per- * user accounting.
6185db853e024a486ff8837e6784dd290d866112dougm if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC|O_EXCL, mode)) < 0)
6185db853e024a486ff8837e6784dd290d866112dougm if((n = open(tmpname,O_WRONLY|O_CREAT|O_TRUNC, mode)) < 0)
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm#endif /* O_EXCL */
6185db853e024a486ff8837e6784dd290d866112dougm /* populate the clone */
25a68471b9ababbc21cfdbbb2866014f34f419ecdougm else if(n == -1)
6185db853e024a486ff8837e6784dd290d866112dougm /* move write end of pipe into FDSYNC */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* wait for child to die */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw while((m = wait(0)) != n)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* Kill any setuid status at this point. That way, if the
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * clone is not setuid, we won't exec it as root. Also, don't
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * neglect to consider that someone could have switched the
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * clone file on us.
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * create a unique name into the <template>
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* skip to end of string */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw while(*++cp);
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw /* convert process id to string */
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw while(n > 0)
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw * copy THISPROG into the open file number <fdo> and close <fdo>
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw register int n;
da6c28aaf62fa55f0fdb8004aa40f88f23bf53f0amw#endif /* _lib_setreuid */