/* * Copyright (c) 2004, Nate Nielsen * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above * copyright notice, this list of conditions and the * following disclaimer. * * Redistributions in binary form must reproduce the * above copyright notice, this list of conditions and * the following disclaimer in the documentation and/or * other materials provided with the distribution. * * The names of contributors to this software may not be * used to endorse or promote products derived from this * software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH * DAMAGE. * * * CONTRIBUTORS * Nate Nielsen * James E. Quick */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" extern char** environ; size_t get_jail_sysctl(struct xprison** ret) { struct xprison* xp; size_t len; *ret = NULL; if(sysctlbyname("security.jail.list", NULL, &len, NULL, 0) == -1) err(1, "couldn't list jails"); retry: if(len <= 0) return 0; xp = calloc(len, 1); if(xp == NULL) err(1, "out of memory"); if(sysctlbyname("security.jail.list", xp, &len, NULL, 0) == -1) { if(errno == ENOMEM) { free(xp); goto retry; } err(1, "couldn't list jails"); } if(len < sizeof(*xp) || len % sizeof(*xp) || xp->pr_version != XPRISON_VERSION) errx(1, "kernel and userland out of sync"); *ret = xp; return len / sizeof(*xp); } void free_jail_sysctl(size_t len, struct xprison* xp) { if(len == 0 || xp == NULL) return; /* * An extra precaution to prevent leakage of information * into the jail. */ memset(xp, 0, sizeof(struct xprison) * len); free(xp); } struct xprison* find_jail(const char* str, size_t len, struct xprison* xp) { int jid; size_t i; char* e; if(len == 0 || xp == NULL) return NULL; jid = strtol(str, &e, 10); /* If it was all a number ... */ if(!*e) { if(jid <= 0) errx(1, "invalid jail id: %s", str); /* Validate the number */ for(i = 0; i < len; i++) { if(jid == xp[i].pr_id) return &(xp[i]); } } for(i = 0; i < len; i++) { if(strcmp(xp[i].pr_host, str) == 0) return &(xp[i]); } return NULL; } int translate_jail_name(const char* str) { struct xprison* sxp = NULL; struct xprison* xp = NULL; size_t len; int jid = -1; len = get_jail_sysctl(&sxp); if(sxp) { xp = find_jail(str, len, sxp); if(xp != NULL) jid = xp->pr_id; free(sxp); } return jid; } int kvm_prepare_jail(struct xprison* xp) { /* * Basically the kvm routines won't work in a jail unless there's * a /dev/null device for us to use as the file names. If it's * missing we have to create it. */ struct stat sb; char* path; int nodir = 0; int nonull = 0; path = (char*)alloca(strlen(_PATH_DEVNULL) + 2 + strlen(xp->pr_path)); strcpy(path, xp->pr_path); strcat(path, _PATH_DEVNULL); if(stat(path, &sb) == -1) { if(errno == ENOTDIR) { nodir = 1; nonull = 1; } else if(errno == ENOENT) { nonull = 1; } else { err(1, "couldn't stat file: %s", path); } } if(nodir) { strcpy(path, xp->pr_path); strcat(path, _PATH_DEV); if(mkdir(path, 0) == -1 || chmod(path, S_IRWXU | S_IRGRP | S_IXGRP | S_IROTH | S_IXOTH) == -1) { warn("couldn't create %s directory", path); return -1; } } if(nonull) { mode_t mode = 0666 | S_IFCHR; dev_t dev = makedev(2, 2); strcpy(path, xp->pr_path); strcat(path, _PATH_DEVNULL); warnx("creating %s device in jail.", path); if(mknod(path, mode, dev) == -1) { warn("couldn't create %s device", path); return -1; } } return 0; } /* * in_jail * This code was written by James E. Quick mailto:jq@quick.com * The code may be freely re-used under the terms of the BSD copyright, * as long as this comment remains intact. */ int running_in_jail() { int count; kvm_t* kd = 0; struct kinfo_proc* kp; int result = -1; kd = kvm_open(_PATH_DEVNULL, _PATH_DEVNULL, NULL, O_RDONLY, NULL); if(kd == NULL) return -1; kp = kvm_getprocs(kd, KERN_PROC_PID, getpid(), &count); if(kp == NULL) result = -1; else result = (kp->ki_flag & P_JAILED) ? 1 : 0; kvm_close(kd); return result; } int check_jail_command(const char* jail, const char* cmd) { struct stat sb; if(stat(cmd, &sb) == -1) { if(errno == EACCES || errno == ELOOP || errno == ENAMETOOLONG || errno == ENOENT || errno == ENOTDIR) { warn("%s%scan't execute in jail: %s", jail ? jail : "", jail ? ": " : "", cmd); return 0; } err(1, "%s%scouldn't stat file: %s", jail ? jail : "", jail ? ": " : "", cmd); } if(!(sb.st_mode & S_IFREG)) { warnx("%s%snot a regular file: %s", jail ? jail : "", jail ? ": " : "", cmd); return 0; } if(sb.st_uid != 0) { warnx("%s%snot owned by root: %s", jail ? jail : "", jail ? ": " : "", cmd); return 0; } return 1; } int run_overlay_command(const char* jail, const char* cmd, char* env[], char* args[]) { if(args) execve(cmd, args, env); else execle(cmd, cmd, NULL, env); warn("%s%serror executing: %s: %s", jail ? jail : "", jail ? ": " : "", cmd); return 0; } int run_simple_command(const char* jail, const char* cmd, char* env[], char* args[], int opts) { pid_t pid; int status = 0; if(opts & JAIL_RUN_NOFORK) return run_overlay_command(jail, cmd, env, args); switch((pid = fork())) { case -1: err(1, "couldn't fork child process"); break; /* This is the child here */ case 0: if(args) execve(cmd, args, env); else execle(cmd, cmd, NULL, env); exit(errno); break; /* This is the parent process */ default: /* If the processes exited then break out */ if(waitpid(pid, &status, 0) == -1) err(1, "couldn't wait on child process"); /* Return any status codes */ if(WEXITSTATUS(status) != 0) { warnx("%s%serror executing: %s: %s", jail ? jail : "", jail ? ": " : "", cmd, strerror(WEXITSTATUS(status))); return 0; } break; } return 1; } /* read & write ends of a pipe */ #define READ_END 0 #define WRITE_END 1 /* pre-set file descriptors */ #define STDIN 0 #define STDOUT 1 #define STDERR 2 int run_dup_command(const char* jail, const char* cmd, char* env[], char* args[], int opts) { int outpipe[2]; pid_t pid; /* * Special function to duplicate output of a command to two * files. * * NOTE: Yes, I know this may seem like overkill, but system, * popen and all those guys would hang with certain rc scripts. * Those which opened a daemon in the background ('&') but still * kept their output going to the same stdin/stdout handles. */ /* Create a pipe for the child process */ if(pipe(outpipe) < 0) return -1; switch(pid = fork()) { case -1: err(1, "couldn't fork child process"); break; /* This is the child here */ case 0: { /* Fix up our end of the pipe */ if(dup2(outpipe[WRITE_END], STDOUT) < 0 || dup2(outpipe[WRITE_END], STDERR) < 0) exit(errno); /* Okay, now run whatever command it was */ if(args) execve(cmd, args, env); else execle(cmd, cmd, NULL, env); /* In case it returns then have to do this to get children to disconnect from stdout */ fflush(stdout); fclose(stdout); close(outpipe[WRITE_END]); exit(errno); } break; /* And this is the parent */ default: { int console = -1; int ret; int status = 0; int waited = 0; fd_set readmask; char buff[256]; struct timeval timeout = { 0, 10000 }; FD_ZERO(&readmask); /* Open the console file and write the header */ if(opts & JAIL_RUN_CONSOLE) console = open(_PATH_CONSOLE, O_WRONLY | O_APPEND); /* No blocking on the child processes pipe */ fcntl(outpipe[READ_END], F_SETFL, fcntl(outpipe[READ_END], F_GETFL, 0) | O_NONBLOCK); /* Loop until the process dies or no more output */ while(1) { FD_SET(outpipe[READ_END], &readmask); if(select(FD_SETSIZE, &readmask, NULL, NULL, &timeout) == -1) err(1, "couldn't select"); if(FD_ISSET(outpipe[READ_END], &readmask)) { /* Read text */ while((ret = read(outpipe[READ_END], buff, 256)) > 0) { if(opts & JAIL_RUN_STDOUT) write(STDOUT, buff, ret); if(opts & JAIL_RUN_STDERR) write(STDERR, buff, ret); if(console != -1) write(console, buff, ret); } } /* Or if there's an error or end of file */ if(ret == -1 && errno != EAGAIN || ret == 0) break; /* If the processes exited then break out */ if(waitpid(pid, &status, WNOHANG) == pid) { waited = 1; break; } } if(!waited) waitpid(pid, &status, 0); /* Return any status codes */ if(WEXITSTATUS(status) != 0) { warnx("%s%serror executing: %s: %s", jail ? jail : "", jail ? ": " : "", cmd, strerror(WEXITSTATUS(status))); return 0; } /* Clean up */ close(outpipe[READ_END]); if(console != -1) close(console); } break; } return 1; } int run_jail_command(const char* jail, const char* cmd, char* args[], int opts) { char* env[5]; char* t; int j; memset(env, 0, sizeof(env)); #define MAKE_ENV_VAR(n) \ t = getenv(n); \ if(t != NULL) \ { \ env[j] = alloca(strlen(n) + 2 + strlen(t)); \ sprintf(env[j], "%s=%s", (char*)(n), t); \ j++; \ } /* Prepare an environment for the cmd */ env[0] = "PATH=" _PATH_STDPATH; j = 1; MAKE_ENV_VAR("TERM"); MAKE_ENV_VAR("COLUMNS"); MAKE_ENV_VAR("LINES"); if(opts & JAIL_RUN_OUTPUT) return run_dup_command(jail, cmd, env, args, opts); else return run_simple_command(jail, cmd, env, args, opts); }