/* * Initialise the child context of a sandbox. * Each sandbox will want to do something here to make sure that the * child context is sandboxed properly. * This function depends on "type": if SAND_WORKER, we set fd1 to be the * descriptor between the child and the application; if fd2 isn't -1, * it's the FastCGI control connection (fdfiled and fdaccept should be * ignored in SAND_WORKER case). * If not SAND_WORKER, we're the control process in a FastCGI context: * fd1 is the control connection; fd2 is -1; fdaccept, if not -1, is the * old-style FastCGI socket; fdfiled, if not -1, is the new-style * transport descriptor interface. * Whew! */ int ksandbox_init_child(void *arg, enum sandtype type, int fd1, int fd2, int fdfiled, int fdaccept) { #if defined(HAVE_CAPSICUM) if ( ! ksandbox_capsicum_init_child(arg, type, fd1, fd2, fdfiled, fdaccept)) { XWARNX("ksandbox_capsicum_init_child"); return(0); } #elif defined(HAVE_SANDBOX_INIT) if ( ! ksandbox_darwin_init_child(arg, type)) { XWARNX("ksandbox_darwin_init_child"); return(0); } #elif defined(HAVE_PLEDGE) if ( ! ksandbox_pledge_init_child(arg, type)) { XWARNX("ksandbox_pledge_init_child"); return(0); } #elif defined(HAVE_SYSTRACE) if ( ! ksandbox_systrace_init_child(arg, type)) { XWARNX("ksandbox_systrace_init_child"); return(0); } #elif defined(HAVE_SECCOMP_FILTER) if ( ! ksandbox_seccomp_init_child(arg, type)) { XWARNX("ksandbox_seccomp_init_child"); return(0); } #endif return(1); }
static int ksandbox_capsicum_init_control(void *arg, int fd1, int fd2) { int rc; struct rlimit rl_zero; cap_rights_t rights; cap_rights_init(&rights); cap_rights_init(&rights, CAP_EVENT, CAP_FCNTL, CAP_ACCEPT); if (cap_rights_limit(STDIN_FILENO, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: STDIN_FILENO"); return(0); } cap_rights_init(&rights, CAP_WRITE, CAP_FSTAT); if (cap_rights_limit(STDERR_FILENO, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: STDERR_FILENO"); return(0); } cap_rights_init(&rights, CAP_EVENT, CAP_FCNTL, CAP_READ, CAP_WRITE); if (cap_rights_limit(fd1, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: internal socket"); return(0); } rl_zero.rlim_cur = rl_zero.rlim_max = 0; if (-1 == setrlimit(RLIMIT_FSIZE, &rl_zero)) { XWARNX("setrlimit: rlimit_fsize"); return(0); } else if (-1 == setrlimit(RLIMIT_NPROC, &rl_zero)) { XWARNX("setrlimit: rlimit_nproc"); return(0); } rc = cap_enter(); if (0 != rc && errno != ENOSYS) { XWARN("cap_enter"); rc = 0; } else rc = 1; return(rc); }
int ksandbox_capsicum_init_child(void *arg, enum sandtype type, int fd1, int fd2, int fdfiled, int fdaccept) { int rc; switch (type) { case (SAND_WORKER): rc = ksandbox_capsicum_init_worker(arg, fd1, fd2); break; case (SAND_CONTROL_OLD): assert(-1 == fd2); rc = ksandbox_capsicum_init_control (arg, fd1, fdfiled, fdaccept); break; case (SAND_CONTROL_NEW): assert(-1 == fd2); rc = ksandbox_capsicum_init_control (arg, fd1, fdfiled, fdaccept); break; default: abort(); } if ( ! rc) XWARNX("capsicum sandbox failure"); return(rc); }
void ksandbox_systrace_close(void *arg) { struct systrace_sandbox *box = arg; /* Closing this before the child exits will terminate it */ if (-1 != box->systrace_fd) close(box->systrace_fd); else XWARNX("systrace fd not opened"); }
int ksandbox_systrace_init_child(void *arg, enum sandtype type) { struct systrace_sandbox *box = arg; if (NULL == arg) { XWARNX("systrace child passed null config"); return(0); } signal(SIGCHLD, box->osigchld); if (kill(getpid(), SIGSTOP) == 0) return(1); XWARN("kill: SIGSTOP"); return(0); }
/* * Perform system-specific initialisation for the parent. * This is only used by systrace(4), which requires the parent to * register the child's systrace hooks. */ int ksandbox_init_parent(void *arg, enum sandtype type, pid_t child) { #if defined(HAVE_CAPSICUM) return(1); #elif defined(HAVE_SANDBOX_INIT) return(1); #elif defined(HAVE_PLEDGE) return(1); #elif defined(HAVE_SYSTRACE) if ( ! ksandbox_systrace_init_parent(arg, type, child)) { XWARNX("ksandbox_systrace_init_child"); return(0); } #elif defined(HAVE_SECCOMP_FILTER) return(1); #endif return(1); }
int ksandbox_seccomp_init_child(void *arg, enum sandtype type) { struct rlimit rl_zero; int nnp_failed = 0; /* Set rlimits for completeness if possible. */ rl_zero.rlim_cur = rl_zero.rlim_max = 0; if (setrlimit(RLIMIT_FSIZE, &rl_zero) == -1) XWARN("setrlimit(RLIMIT_FSIZE)"); #if 0 /* * Don't do like OpenSSH: we need to pass stuff back and forth * over pipes, and this will prevent that from happening. */ if (setrlimit(RLIMIT_NOFILE, &rl_zero) == -1) XWARN("setrlimit(RLIMIT_NOFILE)"); #endif if (setrlimit(RLIMIT_NPROC, &rl_zero) == -1) XWARN("setrlimit(RLIMIT_NPROC)"); #ifdef SANDBOX_SECCOMP_DEBUG ssh_sandbox_child_debugging(); #endif if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) { XWARN("prctl(PR_SET_NO_NEW_PRIVS)"); nnp_failed = 1; } if (prctl(PR_SET_SECCOMP, SECCOMP_MODE_FILTER, SAND_WORKER != type ? &preauth_prog_ctrl : &preauth_prog_work) == -1) XWARN("prctl(PR_SET_SECCOMP)"); else if (nnp_failed) { XWARNX("SECCOMP_MODE_FILTER activated but " "PR_SET_NO_NEW_PRIVS failed"); _exit(EXIT_FAILURE); } return(1); }
/* * Allocate system-specific data for the sandbox. * This is only used by systrace, which requires extra accounting * information for the child. */ int ksandbox_alloc(void **pp) { *pp = NULL; #if defined(HAVE_CAPSICUM) return(1); #elif defined(HAVE_SANDBOX_INIT) return(1); #elif defined(HAVE_PLEDGE) return(1); #elif defined(HAVE_SYSTRACE) if (NULL == (*pp = (ksandbox_systrace_alloc()))) { XWARNX("ksandbox_systrace_alloc"); return(0); } #elif defined(HAVE_SECCOMP_FILTER) return(1); #endif return(1); }
int ksandbox_darwin_init_child(void *arg, enum sandtype type) { int rc; char *er; struct rlimit rl_zero; rc = SAND_WORKER == type ? sandbox_init(kSBXProfilePureComputation, SANDBOX_NAMED, &er) : sandbox_init(kSBXProfileNoWrite, SANDBOX_NAMED, &er); if (0 != rc) { XWARNX("sandbox_init: %s", er); sandbox_free_error(er); rc = 0; } else rc = 1; rl_zero.rlim_cur = rl_zero.rlim_max = 0; #if 0 /* * FIXME: I've taken out the RLIMIT_NOFILE setrlimit() because * it causes strange behaviour. On Mac OS X, it fails with * EPERM no matter what (the same code runs fine when not run as * a CGI instance). */ if (-1 == setrlimit(RLIMIT_NOFILE, &rl_zero)) XWARN("setrlimit: rlimit_fsize"); #endif if (-1 == setrlimit(RLIMIT_FSIZE, &rl_zero)) XWARN("setrlimit: rlimit_fsize"); if (-1 == setrlimit(RLIMIT_NPROC, &rl_zero)) XWARN("setrlimit: rlimit_nproc"); return(rc); }
/* * Read a single kpair from the child. * This returns 0 if there are no more pairs to read and -1 if any * errors occur (the parent should also exit with server failure). * Otherwise, it returns 1 and the pair is zeroed and filled in. */ static int input(enum input *type, struct kpair *kp, int fd, enum kcgi_err *ke, int eofok) { size_t sz; int rc; ptrdiff_t diff; memset(kp, 0, sizeof(struct kpair)); /* This will return EOF for the last one. */ rc = fullread(fd, type, sizeof(enum input), 1, ke); if (0 == rc) { if (eofok) return(0); XWARNX("unexpected eof from child"); *ke = KCGI_FORM; return(-1); } else if (rc < 0) return(-1); if (*type == IN__MAX) return(0); if (*type > IN__MAX) { XWARNX("unknown input type %d", *type); *ke = KCGI_FORM; return(-1); } /* TODO: check additive overflow. */ if (fullread(fd, &sz, sizeof(size_t), 0, ke) < 0) return(-1); if (NULL == (kp->key = XCALLOC(sz + 1, 1))) { *ke = KCGI_ENOMEM; return(-1); } if (fullread(fd, kp->key, sz, 0, ke) < 0) return(-1); /* TODO: check additive overflow. */ if (fullread(fd, &kp->valsz, sizeof(size_t), 0, ke) < 0) return(-1); if (NULL == (kp->val = XCALLOC(kp->valsz + 1, 1))) { *ke = KCGI_ENOMEM; return(-1); } if (fullread(fd, kp->val, kp->valsz, 0, ke) < 0) return(-1); if (fullread(fd, &kp->state, sizeof(enum kpairstate), 0, ke) < 0) return(-1); if (fullread(fd, &kp->type, sizeof(enum kpairtype), 0, ke) < 0) return(-1); if (fullread(fd, &kp->keypos, sizeof(size_t), 0, ke) < 0) return(-1); if (KPAIR_VALID == kp->state) switch (kp->type) { case (KPAIR_DOUBLE): if (fullread(fd, &kp->parsed.d, sizeof(double), 0, ke) < 0) return(-1); break; case (KPAIR_INTEGER): if (fullread(fd, &kp->parsed.i, sizeof(int64_t), 0, ke) < 0) return(-1); break; case (KPAIR_STRING): if (fullread(fd, &diff, sizeof(ptrdiff_t), 0, ke) < 0) return(-1); if (diff > (ssize_t)kp->valsz) { *ke = KCGI_FORM; return(-1); } kp->parsed.s = kp->val + diff; break; default: break; } /* TODO: check additive overflow. */ if (fullread(fd, &sz, sizeof(size_t), 0, ke) < 0) return(-1); if (NULL == (kp->file = XCALLOC(sz + 1, 1))) { *ke = KCGI_ENOMEM; return(-1); } if (fullread(fd, kp->file, sz, 0, ke) < 0) return(-1); /* TODO: check additive overflow. */ if (fullread(fd, &sz, sizeof(size_t), 0, ke) < 0) return(-1); if (NULL == (kp->ctype = XCALLOC(sz + 1, 1))) { *ke = KCGI_ENOMEM; return(-1); } if (fullread(fd, kp->ctype, sz, 0, ke) < 0) return(-1); if (fullread(fd, &kp->ctypepos, sizeof(size_t), 0, ke) < 0) return(-1); /* TODO: check additive overflow. */ if (fullread(fd, &sz, sizeof(size_t), 0, ke) < 0) return(-1); if (NULL == (kp->xcode = XCALLOC(sz + 1, 1))) { *ke = KCGI_ENOMEM; return(-1); } if (fullread(fd, kp->xcode, sz, 0, ke) < 0) return(-1); return(1); }
/* * This is the parent kcgi process. * It spins on input from the child until all fields have been received. * These fields are sent from the child's output() function. * Each input field consists of the data and its validation state. * We build up the kpair arrays here with this data, then assign the * kpairs into named buckets. */ enum kcgi_err kworker_parent(int fd, struct kreq *r, int eofok) { struct kpair kp; struct kpair *kpp; enum krequ requ; enum input type; int rc; enum kcgi_err ke; size_t i, dgsz; /* Pointers freed at "out" label. */ memset(&kp, 0, sizeof(struct kpair)); /* * First read all of our parsed parameters. * Each parsed parameter is handled a little differently. * This list will end with META__MAX. */ if (fullread(fd, &r->reqsz, sizeof(size_t), 0, &ke) < 0) { XWARNX("failed to read request header size"); goto out; } r->reqs = XCALLOC(r->reqsz, sizeof(struct khead)); if (NULL == r->reqs) { ke = KCGI_ENOMEM; goto out; } for (i = 0; i < r->reqsz; i++) { if (fullread(fd, &requ, sizeof(enum krequ), 0, &ke) < 0) { XWARNX("failed to read request identifier"); goto out; } if (KCGI_OK != (ke = fullreadword(fd, &r->reqs[i].key))) { XWARNX("failed to read request key"); goto out; } if (KCGI_OK != (ke = fullreadword(fd, &r->reqs[i].val))) { XWARNX("failed to read request value"); goto out; } if (requ != KREQU__MAX) r->reqmap[requ] = &r->reqs[i]; } if (fullread(fd, &r->method, sizeof(enum kmethod), 0, &ke) < 0) { XWARNX("failed to read request method"); goto out; } else if (fullread(fd, &r->auth, sizeof(enum kauth), 0, &ke) < 0) { XWARNX("failed to read authorisation type"); goto out; } else if (KCGI_OK != (ke = kworker_auth_parent(fd, &r->rawauth))) { XWARNX("failed to read raw authorisation"); goto out; } else if (fullread(fd, &r->scheme, sizeof(enum kscheme), 0, &ke) < 0) { XWARNX("failed to read scheme"); goto out; } else if (KCGI_OK != (ke = fullreadword(fd, &r->remote))) { XWARNX("failed to read remote"); goto out; } else if (KCGI_OK != (ke = fullreadword(fd, &r->fullpath))) { XWARNX("failed to read fullpath"); goto out; } else if (KCGI_OK != (ke = fullreadword(fd, &r->suffix))) { XWARNX("failed to read suffix"); goto out; } else if (KCGI_OK != (ke = fullreadword(fd, &r->pagename))) { XWARNX("failed to read page part"); goto out; } else if (KCGI_OK != (ke = fullreadword(fd, &r->path))) { XWARNX("failed to read path part"); goto out; } else if (KCGI_OK != (ke = fullreadword(fd, &r->pname))) { XWARNX("failed to read script name"); goto out; } else if (KCGI_OK != (ke = fullreadword(fd, &r->host))) { XWARNX("failed to read host name"); goto out; } else if (fullread(fd, &r->port, sizeof(uint16_t), 0, &ke) < 0) { XWARNX("failed to read port"); goto out; } else if (fullread(fd, &dgsz, sizeof(size_t), 0, &ke) < 0) { XWARNX("failed to read digest length"); goto out; } else if (MD5_DIGEST_LENGTH == dgsz) { /* This is a binary value. */ if (NULL == (r->rawauth.digest = XMALLOC(dgsz))) goto out; if (fullread(fd, r->rawauth.digest, dgsz, 0, &ke) < 0) { XWARNX("failed to read digest"); goto out; } } while ((rc = input(&type, &kp, fd, &ke, eofok)) > 0) { assert(type < IN__MAX); /* * We have a parsed field from the child process. * Begin by expanding the number of parsed fields * depending on whether we have a cookie or form input. * Then copy the new data. */ kpp = IN_COOKIE == type ? kpair_expand(&r->cookies, &r->cookiesz) : kpair_expand(&r->fields, &r->fieldsz); if (NULL == kpp) { rc = -1; ke = KCGI_ENOMEM; break; } *kpp = kp; } if (rc < 0) goto out; /* * Now that the field and cookie arrays are fixed and not going * to be reallocated any more, we run through both arrays and * assign the named fields into buckets. */ for (i = 0; i < r->fieldsz; i++) { kpp = &r->fields[i]; if (kpp->keypos == r->keysz) continue; if (KPAIR_INVALID != kpp->state) { kpp->next = r->fieldmap[kpp->keypos]; r->fieldmap[kpp->keypos] = kpp; } else { kpp->next = r->fieldnmap[kpp->keypos]; r->fieldnmap[kpp->keypos] = kpp; } } for (i = 0; i < r->cookiesz; i++) { kpp = &r->cookies[i]; if (kpp->keypos == r->keysz) continue; if (KPAIR_INVALID != kpp->state) { kpp->next = r->cookiemap[kpp->keypos]; r->cookiemap[kpp->keypos] = kpp; } else { kpp->next = r->cookienmap[kpp->keypos]; r->cookienmap[kpp->keypos] = kpp; } } ke = KCGI_OK; /* * Usually, "kp" would be zeroed after its memory is copied into * one of the form-input arrays. * However, in the case of error, these may still have * allocations, so free them now. */ out: free(kp.key); free(kp.val); free(kp.file); free(kp.ctype); free(kp.xcode); return(ke); }
static int ksandbox_capsicum_init_control(void *arg, int worker, int fdfiled, int fdaccept) { int rc; struct rlimit rl_zero; cap_rights_t rights; cap_rights_init(&rights); if (-1 != fdaccept) { /* * If we have old-style accept FastCGI sockets, then * mark us as accepting on it. */ cap_rights_init(&rights, CAP_EVENT, CAP_FCNTL, CAP_ACCEPT); if (cap_rights_limit(fdaccept, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: accept socket"); return(0); } } else { /* New-style descriptor-passing socket. */ assert(-1 != fdfiled); cap_rights_init(&rights, CAP_EVENT, CAP_FCNTL, CAP_READ, CAP_WRITE); if (cap_rights_limit(fdfiled, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: descriptor socket"); return(0); } } /* Always pass through write-only stderr. */ cap_rights_init(&rights, CAP_WRITE, CAP_FSTAT); if (cap_rights_limit(STDERR_FILENO, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: STDERR_FILENO"); return(0); } /* Interface to worker. */ cap_rights_init(&rights, CAP_EVENT, CAP_FCNTL, CAP_READ, CAP_WRITE); if (cap_rights_limit(worker, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: internal socket"); return(0); } rl_zero.rlim_cur = rl_zero.rlim_max = 0; if (-1 == setrlimit(RLIMIT_FSIZE, &rl_zero)) { XWARNX("setrlimit: rlimit_fsize"); return(0); } else if (-1 == setrlimit(RLIMIT_NPROC, &rl_zero)) { XWARNX("setrlimit: rlimit_nproc"); return(0); } rc = cap_enter(); if (0 != rc && errno != ENOSYS) { XWARN("cap_enter"); rc = 0; } else rc = 1; return(rc); }
static int ksandbox_capsicum_init_worker(void *arg, int fd1, int fd2) { int rc; struct rlimit rl_zero; cap_rights_t rights; cap_rights_init(&rights); /* * Test for EBADF because STDIN_FILENO is usually closed by the * caller. */ cap_rights_init(&rights, CAP_EVENT, CAP_READ, CAP_FSTAT); if (cap_rights_limit(STDIN_FILENO, &rights) < 0 && errno != ENOSYS && errno != EBADF) { XWARN("cap_rights_limit: STDIN_FILENO"); return(0); } cap_rights_init(&rights, CAP_EVENT, CAP_WRITE, CAP_FSTAT); if (cap_rights_limit(STDERR_FILENO, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: STDERR_FILENO"); return(0); } cap_rights_init(&rights, CAP_EVENT, CAP_READ, CAP_WRITE, CAP_FSTAT); if (-1 != fd1 && cap_rights_limit(fd1, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: internal socket"); return(0); } if (-1 != fd2 && cap_rights_limit(fd2, &rights) < 0 && errno != ENOSYS) { XWARN("cap_rights_limit: internal socket"); return(0); } rl_zero.rlim_cur = rl_zero.rlim_max = 0; if (-1 == setrlimit(RLIMIT_NOFILE, &rl_zero)) { XWARNX("setrlimit: rlimit_fsize"); return(0); } else if (-1 == setrlimit(RLIMIT_FSIZE, &rl_zero)) { XWARNX("setrlimit: rlimit_fsize"); return(0); } else if (-1 == setrlimit(RLIMIT_NPROC, &rl_zero)) { XWARNX("setrlimit: rlimit_nproc"); return(0); } rc = cap_enter(); if (0 != rc && errno != ENOSYS) { XWARN("cap_enter"); rc = 0; } else rc = 1; return(rc); }
int ksandbox_systrace_init_parent(void *arg, enum sandtype type, pid_t child) { struct systrace_sandbox *box = arg; int dev, i, j, found, st, rc; pid_t pid; struct systrace_policy policy; const struct systrace_preauth *preauth; assert(NULL != arg); preauth = SAND_WORKER != type ? preauth_control : preauth_worker; rc = 0; /* * Wait for the child to send itself a SIGSTOP. * When we receive it, the child is waiting to be sandboxed. */ do pid = waitpid(child, &st, WUNTRACED); while (pid == -1 && errno == EINTR); if (-1 == pid) { XWARN("waitpid"); return(0); } /* Catch if it exits. */ signal(SIGCHLD, box->osigchld); if ( ! WIFSTOPPED(st)) { if (WIFSIGNALED(st)) { XWARNX("child signal %d", WTERMSIG(st)); return(0); } else if (WIFEXITED(st)) { XWARNX("child exit %d", WEXITSTATUS(st)); return(0); } XWARNX("child not stopped"); return(0); } box->child_pid = child; /* Set up systracing of child */ if ((dev = open("/dev/systrace", O_RDONLY)) == -1) { if (ENXIO == errno) { XWARN("open: /dev/systrace (mounted nodev?)"); goto out; } XWARN("open: /dev/systrace"); goto out; } if (ioctl(dev, STRIOCCLONE, &box->systrace_fd) == -1) { XWARN("ioctl: STRIOCCLONE"); close(dev); goto out; } close(dev); if (ioctl(box->systrace_fd, STRIOCATTACH, &child) == -1) { XWARN("ioctl: STRIOCATTACH"); goto out; } /* Allocate and assign policy */ memset(&policy, 0, sizeof(policy)); policy.strp_op = SYSTR_POLICY_NEW; policy.strp_maxents = SYS_MAXSYSCALL; if (ioctl(box->systrace_fd, STRIOCPOLICY, &policy) == -1) { XWARN("ioctl: STRIOCPOLICY (new)"); goto out; } policy.strp_op = SYSTR_POLICY_ASSIGN; policy.strp_pid = box->child_pid; if (ioctl(box->systrace_fd, STRIOCPOLICY, &policy) == -1) { XWARN("ioctl: STRIOCPOLICY (assign)"); goto out; } /* Set per-syscall policy */ for (i = 0; i < SYS_MAXSYSCALL; i++) { found = 0; for (j = 0; preauth[j].syscall != -1; j++) { if (preauth[j].syscall == i) { found = 1; break; } } policy.strp_op = SYSTR_POLICY_MODIFY; policy.strp_code = i; policy.strp_policy = found ? preauth[j].action : SYSTR_POLICY_KILL; if (ioctl(box->systrace_fd, STRIOCPOLICY, &policy) == -1) { XWARN("ioctl: STRIOCPOLICY (modify)"); goto out; } } rc = 1; out: /* Signal the child to start running */ if (kill(box->child_pid, SIGCONT) == 0) return(rc); XWARN("kill: SIGCONT"); return(0); }