/* * $Id: runchroot.c$ * * written by : Stephen J Friedl * Software Consultant * Tustin, California USA * www.unixwiz.net * * This program is a simple front end to the chroot command that * launches a jailed program with an appropriate working directory, * user, and group IDs. It does require that the user have set up * the jail properly, with needed shared libraries and the like in * place. * * COMMAND-LINE PARAMS * ------------------- * * The format of this command is: * * runchroot [options] -u USER -- command arg arg arg * * Where: * * -v Add a bit of verbose output to stderr * -u USER Look up USER in passwd file for uid/gid/directory * -d DIR Use DIR for root instead of homedir from passwd file * -- Marks the end of the "runchroot" parameters * * The command that follows is "execv"'d directly with all the * parameters given. No inspection of the command is performed * other than to insure that it's present. * * Requesting a user with -uUSER looks up the user information in * the full system's /etc/passwd file, and from this the user ID, * group ID and home/working directory are used. If -dDIR is used, * this overrides the homee directory from the password file. It's * a fatal error if the user option is not given. */ #include #include #include #include #include #include #include #include #ifndef __GNUC__ # define __attribute__(x) /* nothing */ #endif static void die(const char *format, ...) __attribute__((noreturn, format(printf,1,2))); static const char *user_name = 0, *chroot_dir = 0; static int Verbose = 0; int main(int argc, char **argv) { struct passwd *pwent; int i; int c; int fdmax; while ( (c = getopt(argc, argv, "u:d:v")) != EOF ) { switch (c) { case 'u': user_name = optarg; break; case 'd': chroot_dir = optarg; break; case 'v': Verbose++; break; default: die("ERROR: -%c is invalid cmdline param", optopt); } } /*---------------------------------------------------------------- * SANITY CHECKING OF PARAMETERS * * We require a user name and command to execute - it's an error * if these are missing. */ if ( user_name == 0 ) { die("ERROR: missing '-u USERNAME' parameter"); } if ( optind >= argc ) { die("ERROR: missing command to run inside the jail"); } argv += optind; argc -= optind; /*---------------------------------------------------------------- * Fetch the user name from the password file to get the id numbers * that we'll be using for this jail, including the home directory. * If the user specified a directory on the cmdline, it overrides * that found in the password file. */ if ( (pwent = getpwnam(user_name)) == 0 ) { die("ERROR: can't find \"%s\" in password file", user_name); } if ( chroot_dir == 0 ) chroot_dir = pwent->pw_dir; if ( Verbose ) { fprintf(stderr, "user=%s uid=%d gid=%d home=%s\n", user_name, pwent->pw_uid, pwent->pw_gid, chroot_dir ); } /*---------------------------------------------------------------- * Change to the target directory and lower the root. Some chroot * implementations automatically change to the given directory * before lowering the root, but we do it explicitly to avoid any * problems with implementations that don't. */ if ( chdir(chroot_dir) != 0 ) { die("ERROR: cannot chdir(%s) [err=%s]", chroot_dir, strerror(errno)); } else if ( chroot(chroot_dir) != 0 ) { die("ERROR: cannot chroot(%s) [err=%s]", chroot_dir, strerror(errno)); } else if ( Verbose ) { fprintf(stderr, "Root lowered to %s OK\n", chroot_dir); } /*---------------------------------------------------------------- * SAFETY PRECAUTION: close all open file descriptors beyond the * customary stdin/stdout/stderr. This forestalls having process * in the jail tripping across a file descriptor to a full-system * process and causing shenanigans. */ fdmax = getdtablesize(); if ( fdmax <= 0 ) fdmax = 256; for (i = 3; i < fdmax; i++) { if (close(i) == 0 && Verbose ) fprintf(stderr, "closed fd(%d) ok\n", i); } if ( setgid(pwent->pw_gid) != 0) { die("ERROR: can't setgid(%d) [err=%s]", pwent->pw_gid, strerror(errno)); } if ( setreuid(pwent->pw_uid, pwent->pw_uid) != 0) { die("ERROR: can't setreuid(%d) [err=%s]", pwent->pw_uid, strerror(errno)); } if ( Verbose ) { fprintf(stderr, "Running: %s", argv[0]); for (i = 1; i < argc; i++ ) { fprintf(stderr, " %s", argv[i]); } putc('\n', stderr); } execv(argv[0], argv); die("ERROR: cannot exec %s [err=%s]", argv[0], strerror(errno)); } /* * die() * * Given a printf-style argument list, format the message to the * standard error stream, add a newline, and exit with error status. */ static void die(const char *format, ...) { va_list args; va_start(args, format); vfprintf(stderr, format, args); va_end(args); putc('\n', stderr); exit(EXIT_FAILURE); }