/** * DESCRIPTION * This code was originally posted by Wietse Venema, years ago, in * a discussion on news on how to create safe suid wrappers. For * those interested in NNTP archeology, here's the post: * * Article 5648 of comp.security.unix: * From: [email protected] (Wietse Venema) * Newsgroups: comp.security.unix * Subject: Re: [8lgm]-Advisory-7.UNIX.passwd.11-May-1994 * Date: 18 May 1994 07:52:05 +0200 * Organization: Eindhoven University of Technology, The Netherlands * Lines: 68 * * [email protected] (H. Milton Johnson) writes: * >OK, I admit it, I'm a totally incompetent sysadmin because I am not * >sure I could write a bullet-proof setuid wrapper. However, if one of * >the competent sysadmins subscribing to this group could post or point * >the way to an example of a bullet- proof setuid wrapper, I'm sure that * >I could use it as a template to address this/future/other problems. * * Ok, here is a first stab. Perhaps we can make this into a combined * effort and get rid of the problem once and for all. * * Wietse * * [code - see the function below, only marginally changed to suit monit] * * @author Wietse Venema <*****@*****.**> * */ static void set_sandbox(void) { int i = 0; struct stat st; extern char **environ; char *path = "PATH=/bin:/usr/bin:/sbin:/usr/sbin"; char *tz; /* * Purge the environment, but keep the TZ variable as the time.h family depends on it at least on AIX */ for (tz = environ[0]; tz; tz = environ[++i]) { if (! strncasecmp(tz, "TZ=", 3)) { environ[0] = tz; environ[1] = 0; break; } } if (! tz) environ[0] = 0; if (putenv(path)) { LogError("%s: cannot set the PATH variable -- %s\n", prog, STRERROR); exit(1); } /* * Require that file descriptors 0,1,2 are open. Mysterious things * can happen if that is not the case. */ for(i= 0; i < 3; i++) { if(fstat(i, &st) == -1 && open("/dev/null", O_RDWR) != i) { LogError("Cannot open /dev/null -- %s\n", STRERROR); exit(1); } } Util_closeFds(); }
/** * Execute the given command. If the execution fails, the wait_start() * thread in control.c should notice this and send an alert message. * @param S A Service object * @param C A Command object * @param E An optional event object. May be NULL. */ void spawn(Service_T S, command_t C, Event_T E) { pid_t pid; sigset_t mask; sigset_t save; int stat_loc = 0; int exit_status; char date[42]; ASSERT(S); ASSERT(C); if(access(C->arg[0], X_OK) != 0) { LogError("Error: Could not execute %s\n", C->arg[0]); return; } /* * Block SIGCHLD */ sigemptyset(&mask); sigaddset(&mask, SIGCHLD); pthread_sigmask(SIG_BLOCK, &mask, &save); Time_string(Time_now(), date); pid = fork(); if(pid < 0) { LogError("Cannot fork a new process -- %s\n", STRERROR); exit(1); } if(pid == 0) { /* * Reset to the original umask so programs will inherit the * same file creation mask monit was started with */ umask(Run.umask); /* * Switch uid/gid if requested */ if(C->has_gid) { if(0 != setgid(C->gid)) { stat_loc |= setgid_ERROR; } } if(C->has_uid) { if(0 != setuid(C->uid)) { stat_loc |= setuid_ERROR; } } set_monit_environment(S, C, E, date); if(! Run.isdaemon) { for(int i = 0; i < 3; i++) if(close(i) == -1 || open("/dev/null", O_RDWR) != i) stat_loc |= redirect_ERROR; } Util_closeFds(); setsid(); pid = fork(); if(pid < 0) { stat_loc |= fork_ERROR; _exit(stat_loc); } if(pid == 0) { /* * Reset all signals, so the spawned process is *not* created * with any inherited SIG_BLOCKs */ sigemptyset(&mask); pthread_sigmask(SIG_SETMASK, &mask, NULL); signal(SIGINT, SIG_DFL); signal(SIGHUP, SIG_DFL); signal(SIGTERM, SIG_DFL); signal(SIGUSR1, SIG_DFL); signal(SIGPIPE, SIG_DFL); (void) execv(C->arg[0], C->arg); _exit(errno); } /* Exit first child and return errors to parent */ _exit(stat_loc); } /* Wait for first child - aka second parent, to exit */ if(waitpid(pid, &stat_loc, 0) != pid) { LogError("Waitpid error\n"); } exit_status = WEXITSTATUS(stat_loc); if (exit_status & setgid_ERROR) LogError("Failed to change gid to '%d' for '%s'\n", C->gid, C->arg[0]); if (exit_status & setuid_ERROR) LogError("Failed to change uid to '%d' for '%s'\n", C->uid, C->arg[0]); if (exit_status & fork_ERROR) LogError("Cannot fork a new process for '%s'\n", C->arg[0]); if (exit_status & redirect_ERROR) LogError("Cannot redirect IO to /dev/null for '%s'\n", C->arg[0]); /* * Restore the signal mask */ pthread_sigmask(SIG_SETMASK, &save, NULL); /* * We do not need to wait for the second child since we forked twice, * the init system-process will wait for it. So we just return */ }