From: deraadt Date: Fri, 9 Oct 2015 01:17:18 +0000 (+0000) Subject: Rename tame() to pledge(). This fairly interface has evolved to be more X-Git-Url: http://artulab.com/gitweb/?a=commitdiff_plain;h=350b464c5787ce73b4bf8e99960537423713261c;p=openbsd Rename tame() to pledge(). This fairly interface has evolved to be more strict than anticipated. It allows a programmer to pledge/promise/covenant that their program will operate within an easily defined subset of the Unix environment, or it pays the price. --- diff --git a/sys/conf/files b/sys/conf/files index 7d3d5501956..04c3ff76556 100644 --- a/sys/conf/files +++ b/sys/conf/files @@ -1,4 +1,4 @@ -# $OpenBSD: files,v 1.603 2015/09/28 08:32:04 mpi Exp $ +# $OpenBSD: files,v 1.604 2015/10/09 01:17:21 deraadt Exp $ # $NetBSD: files,v 1.87 1996/05/19 17:17:50 jonathan Exp $ # @(#)files.newconf 7.5 (Berkeley) 5/10/93 @@ -663,7 +663,7 @@ file kern/kern_physio.c file kern/kern_proc.c file kern/kern_prot.c file kern/kern_resource.c -file kern/kern_tame.c +file kern/kern_pledge.c file kern/kern_sched.c file kern/kern_sensors.c file kern/kern_sig.c diff --git a/sys/kern/kern_pledge.c b/sys/kern/kern_pledge.c new file mode 100644 index 00000000000..9f134b63335 --- /dev/null +++ b/sys/kern/kern_pledge.c @@ -0,0 +1,1241 @@ +/* $OpenBSD: kern_pledge.c,v 1.1 2015/10/09 01:17:21 deraadt Exp $ */ + +/* + * Copyright (c) 2015 Nicholas Marriott + * Copyright (c) 2015 Theo de Raadt + * + * Permission to use, copy, modify, and distribute this software for any + * purpose with or without fee is hereby granted, provided that the above + * copyright notice and this permission notice appear in all copies. + * + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. + */ + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include + +int canonpath(const char *input, char *buf, size_t bufsize); + +const u_int pledge_syscalls[SYS_MAXSYSCALL] = { + [SYS_exit] = 0xffffffff, + [SYS_kbind] = 0xffffffff, + + [SYS_getuid] = PLEDGE_SELF, + [SYS_geteuid] = PLEDGE_SELF, + [SYS_getresuid] = PLEDGE_SELF, + [SYS_getgid] = PLEDGE_SELF, + [SYS_getegid] = PLEDGE_SELF, + [SYS_getresgid] = PLEDGE_SELF, + [SYS_getgroups] = PLEDGE_SELF, + [SYS_getlogin] = PLEDGE_SELF, + [SYS_getpgrp] = PLEDGE_SELF, + [SYS_getpgid] = PLEDGE_SELF, + [SYS_getppid] = PLEDGE_SELF, + [SYS_getsid] = PLEDGE_SELF, + [SYS_getthrid] = PLEDGE_SELF, + [SYS_getrlimit] = PLEDGE_SELF, + [SYS_gettimeofday] = PLEDGE_SELF, + [SYS_getdtablecount] = PLEDGE_SELF, + [SYS_getrusage] = PLEDGE_SELF, + [SYS_issetugid] = PLEDGE_SELF, + [SYS_clock_getres] = PLEDGE_SELF, + [SYS_clock_gettime] = PLEDGE_SELF, + [SYS_getpid] = PLEDGE_SELF, + [SYS_umask] = PLEDGE_SELF, + [SYS_sysctl] = PLEDGE_SELF, /* read-only; narrow subset */ + [SYS_adjtime] = PLEDGE_SELF, /* read-only */ + + [SYS_fchdir] = PLEDGE_SELF, /* careful of directory fd inside jails */ + + /* needed by threaded programs */ + [SYS_sched_yield] = PLEDGE_SELF, + [SYS___thrsleep] = PLEDGE_SELF, + [SYS___thrwakeup] = PLEDGE_SELF, + [SYS___threxit] = PLEDGE_SELF, + [SYS___thrsigdivert] = PLEDGE_SELF, + + [SYS_sendsyslog] = PLEDGE_SELF, + [SYS_nanosleep] = PLEDGE_SELF, + [SYS_sigprocmask] = PLEDGE_SELF, + [SYS_sigaction] = PLEDGE_SELF, + [SYS_sigreturn] = PLEDGE_SELF, + [SYS_sigpending] = PLEDGE_SELF, + [SYS_getitimer] = PLEDGE_SELF, + [SYS_setitimer] = PLEDGE_SELF, + + [SYS_pledge] = PLEDGE_SELF, + + [SYS_wait4] = PLEDGE_SELF, + + [SYS_poll] = PLEDGE_RW, + [SYS_kevent] = PLEDGE_RW, + [SYS_kqueue] = PLEDGE_RW, + [SYS_select] = PLEDGE_RW, + + [SYS_close] = PLEDGE_RW, + [SYS_dup] = PLEDGE_RW, + [SYS_dup2] = PLEDGE_RW, + [SYS_dup3] = PLEDGE_RW, + [SYS_closefrom] = PLEDGE_RW, + [SYS_shutdown] = PLEDGE_RW, + [SYS_read] = PLEDGE_RW, + [SYS_readv] = PLEDGE_RW, + [SYS_pread] = PLEDGE_RW, + [SYS_preadv] = PLEDGE_RW, + [SYS_write] = PLEDGE_RW, + [SYS_writev] = PLEDGE_RW, + [SYS_pwrite] = PLEDGE_RW, + [SYS_pwritev] = PLEDGE_RW, + [SYS_ftruncate] = PLEDGE_RW, + [SYS_lseek] = PLEDGE_RW, + [SYS_fstat] = PLEDGE_RW, + + [SYS_fcntl] = PLEDGE_RW, + [SYS_fsync] = PLEDGE_RW, + [SYS_pipe] = PLEDGE_RW, + [SYS_pipe2] = PLEDGE_RW, + [SYS_socketpair] = PLEDGE_RW, + [SYS_getdents] = PLEDGE_RW, + + [SYS_sendto] = PLEDGE_RW | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, + [SYS_sendmsg] = PLEDGE_RW, + [SYS_recvmsg] = PLEDGE_RW, + [SYS_recvfrom] = PLEDGE_RW | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, + + [SYS_fork] = PLEDGE_PROC, + [SYS_vfork] = PLEDGE_PROC, + [SYS_kill] = PLEDGE_PROC, + [SYS_setpgid] = PLEDGE_PROC, + [SYS_sigsuspend] = PLEDGE_PROC, + [SYS_setrlimit] = PLEDGE_PROC, + + [SYS_execve] = PLEDGE_EXEC, + + [SYS_setgroups] = PLEDGE_PROC, + [SYS_setresgid] = PLEDGE_PROC, + [SYS_setresuid] = PLEDGE_PROC, + + /* FIONREAD/FIONBIO, plus further checks in pledge_ioctl_check() */ + [SYS_ioctl] = PLEDGE_RW | PLEDGE_IOCTL | PLEDGE_TTY, + + [SYS_getentropy] = PLEDGE_MALLOC, + [SYS_madvise] = PLEDGE_MALLOC, + [SYS_minherit] = PLEDGE_MALLOC, + [SYS_mmap] = PLEDGE_MALLOC, + [SYS_mprotect] = PLEDGE_MALLOC, + [SYS_mquery] = PLEDGE_MALLOC, + [SYS_munmap] = PLEDGE_MALLOC, + + [SYS_open] = PLEDGE_SELF, /* further checks in namei */ + [SYS_stat] = PLEDGE_SELF, /* further checks in namei */ + [SYS_access] = PLEDGE_SELF, /* further checks in namei */ + [SYS_readlink] = PLEDGE_SELF, /* further checks in namei */ + + [SYS_chdir] = PLEDGE_RPATH, + [SYS_openat] = PLEDGE_RPATH | PLEDGE_WPATH, + [SYS_fstatat] = PLEDGE_RPATH | PLEDGE_WPATH, + [SYS_faccessat] = PLEDGE_RPATH | PLEDGE_WPATH, + [SYS_readlinkat] = PLEDGE_RPATH | PLEDGE_WPATH, + [SYS_lstat] = PLEDGE_RPATH | PLEDGE_WPATH | PLEDGE_TMPPATH, + [SYS_rename] = PLEDGE_CPATH, + [SYS_rmdir] = PLEDGE_CPATH, + [SYS_renameat] = PLEDGE_CPATH, + [SYS_link] = PLEDGE_CPATH, + [SYS_linkat] = PLEDGE_CPATH, + [SYS_symlink] = PLEDGE_CPATH, + [SYS_unlink] = PLEDGE_CPATH | PLEDGE_TMPPATH, + [SYS_unlinkat] = PLEDGE_CPATH, + [SYS_mkdir] = PLEDGE_CPATH, + [SYS_mkdirat] = PLEDGE_CPATH, + + /* + * Classify as RPATH|WPATH, because of path information leakage. + * WPATH due to unknown use of mk*temp(3) on non-/tmp paths.. + */ + [SYS___getcwd] = PLEDGE_RPATH | PLEDGE_WPATH, + + /* Classify as RPATH, because these leak path information */ + [SYS_getfsstat] = PLEDGE_RPATH, + [SYS_statfs] = PLEDGE_RPATH, + [SYS_fstatfs] = PLEDGE_RPATH, + + [SYS_utimes] = PLEDGE_FATTR, + [SYS_futimes] = PLEDGE_FATTR, + [SYS_utimensat] = PLEDGE_FATTR, + [SYS_futimens] = PLEDGE_FATTR, + [SYS_chmod] = PLEDGE_FATTR, + [SYS_fchmod] = PLEDGE_FATTR, + [SYS_fchmodat] = PLEDGE_FATTR, + [SYS_chflags] = PLEDGE_FATTR, + [SYS_chflagsat] = PLEDGE_FATTR, + [SYS_chown] = PLEDGE_FATTR, + [SYS_fchownat] = PLEDGE_FATTR, + [SYS_lchown] = PLEDGE_FATTR, + [SYS_fchown] = PLEDGE_FATTR, + + [SYS_socket] = PLEDGE_INET | PLEDGE_UNIX | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, + [SYS_connect] = PLEDGE_INET | PLEDGE_UNIX | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, + + [SYS_listen] = PLEDGE_INET | PLEDGE_UNIX, + [SYS_bind] = PLEDGE_INET | PLEDGE_UNIX, + [SYS_accept4] = PLEDGE_INET | PLEDGE_UNIX, + [SYS_accept] = PLEDGE_INET | PLEDGE_UNIX, + [SYS_getpeername] = PLEDGE_INET | PLEDGE_UNIX, + [SYS_getsockname] = PLEDGE_INET | PLEDGE_UNIX, + [SYS_setsockopt] = PLEDGE_INET | PLEDGE_UNIX, + [SYS_getsockopt] = PLEDGE_INET | PLEDGE_UNIX, + + [SYS_flock] = PLEDGE_GETPW, +}; + +static const struct { + char *name; + int flags; +} pledgereq[] = { + { "malloc", PLEDGE_SELF | PLEDGE_MALLOC }, + { "rw", PLEDGE_SELF | PLEDGE_RW }, + { "stdio", PLEDGE_SELF | PLEDGE_MALLOC | PLEDGE_RW }, + { "rpath", PLEDGE_SELF | PLEDGE_RW | PLEDGE_RPATH }, + { "wpath", PLEDGE_SELF | PLEDGE_RW | PLEDGE_WPATH }, + { "tmppath", PLEDGE_SELF | PLEDGE_RW | PLEDGE_TMPPATH }, + { "inet", PLEDGE_SELF | PLEDGE_RW | PLEDGE_INET }, + { "unix", PLEDGE_SELF | PLEDGE_RW | PLEDGE_UNIX }, + { "dns", PLEDGE_SELF | PLEDGE_MALLOC | PLEDGE_DNSPATH }, + { "getpw", PLEDGE_SELF | PLEDGE_MALLOC | PLEDGE_RW | PLEDGE_GETPW }, +/*X*/ { "cmsg", PLEDGE_UNIX | PLEDGE_INET | PLEDGE_SENDFD | PLEDGE_RECVFD }, + { "sendfd", PLEDGE_RW | PLEDGE_SENDFD }, + { "recvfd", PLEDGE_RW | PLEDGE_RECVFD }, + { "ioctl", PLEDGE_IOCTL }, + { "route", PLEDGE_ROUTE }, + { "mcast", PLEDGE_MCAST }, + { "tty", PLEDGE_TTY }, + { "proc", PLEDGE_PROC }, + { "exec", PLEDGE_EXEC }, + { "cpath", PLEDGE_CPATH }, + { "abort", PLEDGE_ABORT }, + { "fattr", PLEDGE_FATTR }, + { "prot_exec", PLEDGE_PROTEXEC }, +}; + +int +sys_pledge(struct proc *p, void *v, register_t *retval) +{ + struct sys_pledge_args /* { + syscallarg(const char *)request; + syscallarg(const char **)paths; + } */ *uap = v; + int flags = 0; + int error; + + if (SCARG(uap, request)) { + size_t rbuflen; + char *rbuf, *rp, *pn; + int f, i; + + rbuf = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); + error = copyinstr(SCARG(uap, request), rbuf, MAXPATHLEN, + &rbuflen); + if (error) { + free(rbuf, M_TEMP, MAXPATHLEN); + return (error); + } +#ifdef KTRACE + if (KTRPOINT(p, KTR_STRUCT)) + ktrstruct(p, "pledgereq", rbuf, rbuflen-1); +#endif + + for (rp = rbuf; rp && *rp && error == 0; rp = pn) { + pn = strchr(rp, ' '); /* find terminator */ + if (pn) { + while (*pn == ' ') + *pn++ = '\0'; + } + + for (f = i = 0; i < nitems(pledgereq); i++) { + if (strcmp(rp, pledgereq[i].name) == 0) { + f = pledgereq[i].flags; + break; + } + } + if (f == 0) { + free(rbuf, M_TEMP, MAXPATHLEN); + return (EINVAL); + } + flags |= f; + } + free(rbuf, M_TEMP, MAXPATHLEN); + } + + if (flags & ~PLEDGE_USERSET) + return (EINVAL); + + if ((p->p_p->ps_flags & PS_PLEDGE)) { + /* Already pledged, only allow reductions */ + if (((flags | p->p_p->ps_pledge) & PLEDGE_USERSET) != + (p->p_p->ps_pledge & PLEDGE_USERSET)) { + return (EPERM); + } + + flags &= p->p_p->ps_pledge; + flags &= PLEDGE_USERSET; /* Relearn _ACTIVE */ + } + + if (SCARG(uap, paths)) { + const char **u = SCARG(uap, paths), *sp; + struct whitepaths *wl; + char *cwdpath = NULL, *path; + size_t cwdpathlen = MAXPATHLEN * 4, cwdlen, len, maxargs = 0; + int i, error; + + if (p->p_p->ps_pledgepaths) + return (EPERM); + + /* Count paths */ + for (i = 0; i < PLEDGE_MAXPATHS; i++) { + if ((error = copyin(u + i, &sp, sizeof(sp))) != 0) + return (error); + if (sp == NULL) + break; + } + if (i == PLEDGE_MAXPATHS) + return (E2BIG); + + wl = malloc(sizeof *wl + sizeof(struct whitepath) * (i+1), + M_TEMP, M_WAITOK | M_ZERO); + wl->wl_size = sizeof *wl + sizeof(struct whitepath) * (i+1); + wl->wl_count = i; + wl->wl_ref = 1; + + path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); + + /* Copy in */ + for (i = 0; i < wl->wl_count; i++) { + char *fullpath = NULL, *builtpath = NULL, *canopath = NULL, *cwd; + size_t builtlen = 0; + + if ((error = copyin(u + i, &sp, sizeof(sp))) != 0) + break; + if (sp == NULL) + break; + if ((error = copyinstr(sp, path, MAXPATHLEN, &len)) != 0) + break; +#ifdef KTRACE + if (KTRPOINT(p, KTR_STRUCT)) + ktrstruct(p, "pledgepath", path, len-1); +#endif + + /* If path is relative, prepend cwd */ + if (path[0] != '/') { + if (cwdpath == NULL) { + char *bp, *bpend; + + cwdpath = malloc(cwdpathlen, M_TEMP, M_WAITOK); + bp = &cwdpath[cwdpathlen]; + bpend = bp; + *(--bp) = '\0'; + + error = vfs_getcwd_common(p->p_fd->fd_cdir, + NULL, &bp, cwdpath, cwdpathlen/2, + GETCWD_CHECK_ACCESS, p); + if (error) + break; + cwd = bp; + cwdlen = (bpend - bp); + } + + /* NUL included in cwd component */ + builtlen = cwdlen + 1 + strlen(path); + if (builtlen > PATH_MAX) { + error = ENAMETOOLONG; + break; + } + builtpath = malloc(builtlen, M_TEMP, M_WAITOK); + snprintf(builtpath, builtlen, "%s/%s", cwd, path); + // printf("pledge: builtpath = %s\n", builtpath); + fullpath = builtpath; + } else + fullpath = path; + + canopath = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); + error = canonpath(fullpath, canopath, MAXPATHLEN); + + free(builtpath, M_TEMP, builtlen); + if (error != 0) { + free(canopath, M_TEMP, MAXPATHLEN); + break; + } + + len = strlen(canopath) + 1; + + //printf("pledge: canopath = %s %lld strlen %lld\n", canopath, + // (long long)len, (long long)strlen(canopath)); + + if (maxargs += len > ARG_MAX) { + error = E2BIG; + break; + } + wl->wl_paths[i].name = malloc(len, M_TEMP, M_WAITOK); + memcpy(wl->wl_paths[i].name, canopath, len); + wl->wl_paths[i].len = len; + free(canopath, M_TEMP, MAXPATHLEN); + } + free(path, M_TEMP, MAXPATHLEN); + free(cwdpath, M_TEMP, cwdpathlen); + + if (error) { + for (i = 0; i < wl->wl_count; i++) + free(wl->wl_paths[i].name, + M_TEMP, wl->wl_paths[i].len); + free(wl, M_TEMP, wl->wl_size); + return (error); + } + p->p_p->ps_pledgepaths = wl; +#if 0 + printf("pledge: %s(%d): paths loaded:\n", p->p_comm, p->p_pid); + for (i = 0; i < wl->wl_count; i++) + if (wl->wl_paths[i].name) + printf("pledge: %d=%s %lld\n", i, wl->wl_paths[i].name, + (long long)wl->wl_paths[i].len); +#endif + } + + p->p_p->ps_pledge = flags; + p->p_p->ps_flags |= PS_PLEDGE; + return (0); +} + +int +pledge_check(struct proc *p, int code) +{ + p->p_pledgenote = p->p_pledgeafter = 0; /* XX optimise? */ + p->p_pledge_syscall = code; + + if (code < 0 || code > SYS_MAXSYSCALL - 1) + return (0); + + if (p->p_p->ps_pledge == 0) + return (code == SYS_exit || code == SYS_kbind); + return (p->p_p->ps_pledge & pledge_syscalls[code]); +} + +int +pledge_fail(struct proc *p, int error, int code) +{ + printf("%s(%d): syscall %d\n", p->p_comm, p->p_pid, p->p_pledge_syscall); + if (p->p_p->ps_pledge & PLEDGE_ABORT) { /* Core dump requested */ + struct sigaction sa; + + memset(&sa, 0, sizeof sa); + sa.sa_handler = SIG_DFL; + setsigvec(p, SIGABRT, &sa); + psignal(p, SIGABRT); + } else + psignal(p, SIGKILL); + + p->p_p->ps_pledge = 0; /* Disable all PLEDGE_ flags */ + return (error); +} + +/* + * Need to make it more obvious that one cannot get through here + * without the right flags set + */ +int +pledge_namei(struct proc *p, char *origpath) +{ + char path[PATH_MAX]; + + if (p->p_pledgenote == TMN_COREDUMP) + return (0); /* Allow a coredump */ + + if (canonpath(origpath, path, sizeof(path)) != 0) + return (pledge_fail(p, EPERM, PLEDGE_RPATH)); + + if ((p->p_pledgenote & TMN_FATTR) && + (p->p_p->ps_pledge & PLEDGE_FATTR) == 0) { + printf("%s(%d): inode syscall%d, not allowed\n", + p->p_comm, p->p_pid, p->p_pledge_syscall); + return (pledge_fail(p, EPERM, PLEDGE_FATTR)); + } + + /* Detect what looks like a mkstemp(3) family operation */ + if ((p->p_p->ps_pledge & PLEDGE_TMPPATH) && + (p->p_pledge_syscall == SYS_open) && + (p->p_pledgenote & TMN_CPATH) && + strncmp(path, "/tmp/", sizeof("/tmp/") - 1) == 0) { + return (0); + } + + /* Allow unlinking of a mkstemp(3) file... + * Good opportunity for strict checks here. + */ + if ((p->p_p->ps_pledge & PLEDGE_TMPPATH) && + (p->p_pledge_syscall == SYS_unlink) && + strncmp(path, "/tmp/", sizeof("/tmp/") - 1) == 0) { + return (0); + } + + /* open, mkdir, or other path creation operation */ + if ((p->p_pledgenote & TMN_CPATH) && + ((p->p_p->ps_pledge & PLEDGE_CPATH) == 0)) + return (pledge_fail(p, EPERM, PLEDGE_CPATH)); + + if ((p->p_pledgenote & TMN_WPATH) && + (p->p_p->ps_pledge & PLEDGE_WPATH) == 0) + return (pledge_fail(p, EPERM, PLEDGE_WPATH)); + + /* Read-only paths used occasionally by libc */ + switch (p->p_pledge_syscall) { + case SYS_access: + /* tzset() needs this. */ + if ((p->p_pledgenote == TMN_RPATH) && + strcmp(path, "/etc/localtime") == 0) + return (0); + break; + case SYS_open: + /* getpw* and friends need a few files */ + if ((p->p_pledgenote == TMN_RPATH) && + (p->p_p->ps_pledge & PLEDGE_GETPW)) { + if (strcmp(path, "/etc/spwd.db") == 0) + return (EPERM); + if (strcmp(path, "/etc/pwd.db") == 0) + return (0); + if (strcmp(path, "/etc/group") == 0) + return (0); + } + + /* DNS needs /etc/{resolv.conf,hosts,services}. */ + if ((p->p_pledgenote == TMN_RPATH) && + (p->p_p->ps_pledge & PLEDGE_DNSPATH)) { + if (strcmp(path, "/etc/resolv.conf") == 0) { + p->p_pledgeafter |= TMA_DNSRESOLV; + return (0); + } + if (strcmp(path, "/etc/hosts") == 0) + return (0); + if (strcmp(path, "/etc/services") == 0) + return (0); + } + if ((p->p_pledgenote == TMN_RPATH) && + (p->p_p->ps_pledge & PLEDGE_GETPW)) { + if (strcmp(path, "/var/run/ypbind.lock") == 0) { + p->p_pledgeafter |= TMA_YPLOCK; + return (0); + } + if (strncmp(path, "/var/yp/binding/", + sizeof("/var/yp/binding/") - 1) == 0) + return (0); + } + /* tzset() needs these. */ + if ((p->p_pledgenote == TMN_RPATH) && + strncmp(path, "/usr/share/zoneinfo/", + sizeof("/usr/share/zoneinfo/") - 1) == 0) + return (0); + if ((p->p_pledgenote == TMN_RPATH) && + strcmp(path, "/etc/localtime") == 0) + return (0); + + /* /usr/share/nls/../libc.cat has to succeed for strerror(3). */ + if ((p->p_pledgenote == TMN_RPATH) && + strncmp(path, "/usr/share/nls/", + sizeof("/usr/share/nls/") - 1) == 0 && + strcmp(path + strlen(path) - 9, "/libc.cat") == 0) + return (0); + break; + case SYS_readlink: + /* Allow /etc/malloc.conf for malloc(3). */ + if ((p->p_pledgenote == TMN_RPATH) && + strcmp(path, "/etc/malloc.conf") == 0) + return (0); + break; + case SYS_stat: + /* DNS needs /etc/resolv.conf. */ + if ((p->p_pledgenote == TMN_RPATH) && + (p->p_p->ps_pledge & PLEDGE_DNSPATH)) { + if (strcmp(path, "/etc/resolv.conf") == 0) { + p->p_pledgeafter |= TMA_DNSRESOLV; + return (0); + } + } + break; + } + + /* + * If a whitelist is set, compare canonical paths. Anything + * not on the whitelist gets ENOENT. + */ + if (p->p_p->ps_pledgepaths) { + struct whitepaths *wl = p->p_p->ps_pledgepaths; + char *fullpath, *builtpath = NULL, *canopath = NULL; + size_t builtlen = 0; + int i, error; + + if (origpath[0] != '/') { + char *cwdpath, *cwd, *bp, *bpend; + size_t cwdpathlen = MAXPATHLEN * 4, cwdlen; + + cwdpath = malloc(cwdpathlen, M_TEMP, M_WAITOK); + bp = &cwdpath[cwdpathlen]; + bpend = bp; + *(--bp) = '\0'; + + error = vfs_getcwd_common(p->p_fd->fd_cdir, + NULL, &bp, cwdpath, cwdpathlen/2, + GETCWD_CHECK_ACCESS, p); + if (error) { + free(cwdpath, M_TEMP, cwdpathlen); + return (error); + } + cwd = bp; + cwdlen = (bpend - bp); + + /* NUL included in cwd component */ + builtlen = cwdlen + 1 + strlen(origpath); + builtpath = malloc(builtlen, M_TEMP, M_WAITOK); + snprintf(builtpath, builtlen, "%s/%s", cwd, origpath); + fullpath = builtpath; + free(cwdpath, M_TEMP, cwdpathlen); + + //printf("namei: builtpath = %s %lld strlen %lld\n", builtpath, + // (long long)builtlen, (long long)strlen(builtpath)); + } else + fullpath = path; + + canopath = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); + error = canonpath(fullpath, canopath, MAXPATHLEN); + + free(builtpath, M_TEMP, builtlen); + if (error != 0) { + free(canopath, M_TEMP, MAXPATHLEN); + return (pledge_fail(p, EPERM, PLEDGE_RPATH)); + } + + //printf("namei: canopath = %s strlen %lld\n", canopath, + // (long long)strlen(canopath)); + + error = ENOENT; + for (i = 0; i < wl->wl_count && wl->wl_paths[i].name && error; i++) { + if (strncmp(canopath, wl->wl_paths[i].name, + wl->wl_paths[i].len - 1) == 0) { + u_char term = canopath[wl->wl_paths[i].len - 1]; + + if (term == '\0' || term == '/' || + wl->wl_paths[i].name[1] == '\0') + error = 0; + } + } + free(canopath, M_TEMP, MAXPATHLEN); + return (error); /* Don't hint why it failed */ + } + + if (p->p_p->ps_pledge & PLEDGE_RPATH) + return (0); + if (p->p_p->ps_pledge & PLEDGE_WPATH) + return (0); + if (p->p_p->ps_pledge & PLEDGE_CPATH) + return (0); + + return (pledge_fail(p, EPERM, PLEDGE_RPATH)); +} + +void +pledge_aftersyscall(struct proc *p, int code, int error) +{ + if ((p->p_pledgeafter & TMA_YPLOCK) && error == 0) + atomic_setbits_int(&p->p_p->ps_pledge, PLEDGE_YP_ACTIVE | PLEDGE_INET); + if ((p->p_pledgeafter & TMA_DNSRESOLV) && error == 0) + atomic_setbits_int(&p->p_p->ps_pledge, PLEDGE_DNS_ACTIVE); +} + +/* + * By default, only the advisory cmsg's can be received from the kernel, + * such as TIMESTAMP ntpd. + * + * If PLEDGE_RECVFD is set SCM_RIGHTS is also allowed in for a carefully + * selected set of descriptors (specifically to exclude directories). + * + * This results in a kill upon recv, if some other process on the system + * send a SCM_RIGHTS to an open socket of some sort. That will discourage + * leaving such sockets lying around... + */ +int +pledge_cmsg_recv(struct proc *p, struct mbuf *control) +{ + struct msghdr tmp; + struct cmsghdr *cmsg; + int *fdp, fd; + struct file *fp; + int nfds, i; + + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + /* Scan the cmsg */ + memset(&tmp, 0, sizeof(tmp)); + tmp.msg_control = mtod(control, struct cmsghdr *); + tmp.msg_controllen = control->m_len; + cmsg = CMSG_FIRSTHDR(&tmp); + + while (cmsg != NULL) { + if (cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS) + break; + cmsg = CMSG_NXTHDR(&tmp, cmsg); + } + + /* No SCM_RIGHTS found -> OK */ + if (cmsg == NULL) + return (0); + + if ((p->p_p->ps_pledge & PLEDGE_RECVFD) == 0) + return pledge_fail(p, EPERM, PLEDGE_RECVFD); + + /* In OpenBSD, a CMSG only contains one SCM_RIGHTS. Check it. */ + fdp = (int *)CMSG_DATA(cmsg); + nfds = (cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg))) / + sizeof(struct file *); + for (i = 0; i < nfds; i++) { + struct vnode *vp; + + fd = *fdp++; + fp = fd_getfile(p->p_fd, fd); + if (fp == NULL) + return pledge_fail(p, EBADF, PLEDGE_RECVFD); + + /* Only allow passing of sockets, pipes, and pure files */ + switch (fp->f_type) { + case DTYPE_SOCKET: + case DTYPE_PIPE: + continue; + case DTYPE_VNODE: + vp = (struct vnode *)fp->f_data; + if (vp->v_type == VREG) + continue; + break; + default: + break; + } + return pledge_fail(p, EPERM, PLEDGE_RECVFD); + } + return (0); +} + +/* + * When pledged, default prevents sending of a cmsg. + * + * Unlike pledge_cmsg_recv pledge_cmsg_send is called with individual + * cmsgs one per mbuf. So no need to loop or scan. + */ +int +pledge_cmsg_send(struct proc *p, struct mbuf *control) +{ + struct cmsghdr *cmsg; + int *fdp, fd; + struct file *fp; + int nfds, i; + + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + if ((p->p_p->ps_pledge & PLEDGE_SENDFD) == 0) + return pledge_fail(p, EPERM, PLEDGE_SENDFD); + + /* Scan the cmsg */ + cmsg = mtod(control, struct cmsghdr *); + + /* Contains no SCM_RIGHTS, so OK */ + if (!(cmsg->cmsg_level == SOL_SOCKET && + cmsg->cmsg_type == SCM_RIGHTS)) + return (0); + + /* In OpenBSD, a CMSG only contains one SCM_RIGHTS. Check it. */ + fdp = (int *)CMSG_DATA(cmsg); + nfds = (cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg))) / + sizeof(struct file *); + for (i = 0; i < nfds; i++) { + struct vnode *vp; + + fd = *fdp++; + fp = fd_getfile(p->p_fd, fd); + if (fp == NULL) + return pledge_fail(p, EBADF, PLEDGE_SENDFD); + + /* Only allow passing of sockets, pipes, and pure files */ + switch (fp->f_type) { + case DTYPE_SOCKET: + case DTYPE_PIPE: + continue; + case DTYPE_VNODE: + vp = (struct vnode *)fp->f_data; + if (vp->v_type == VREG) + continue; + break; + default: + break; + } + /* Not allowed to send a bad fd type */ + return pledge_fail(p, EPERM, PLEDGE_SENDFD); + } + return (0); +} + +int +pledge_sysctl_check(struct proc *p, int miblen, int *mib, void *new) +{ + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + if (new) + return (EFAULT); + + /* routing table observation */ + if ((p->p_p->ps_pledge & PLEDGE_ROUTE)) { + if (miblen == 7 && + mib[0] == CTL_NET && mib[1] == PF_ROUTE && + mib[2] == 0 && + (mib[3] == 0 || mib[3] == AF_INET6 || mib[3] == AF_INET) && + mib[4] == NET_RT_DUMP) + return (0); + + if (miblen == 6 && + mib[0] == CTL_NET && mib[1] == PF_ROUTE && + mib[2] == 0 && + (mib[3] == 0 || mib[3] == AF_INET6 || mib[3] == AF_INET) && + mib[4] == NET_RT_TABLE) + return (0); + + if (miblen == 7 && /* exposes MACs */ + mib[0] == CTL_NET && mib[1] == PF_ROUTE && + mib[2] == 0 && mib[3] == AF_INET && + mib[4] == NET_RT_FLAGS && mib[5] == RTF_LLINFO) + return (0); + } + + if ((p->p_p->ps_pledge & (PLEDGE_ROUTE | PLEDGE_INET))) { + if (miblen == 6 && /* getifaddrs() */ + mib[0] == CTL_NET && mib[1] == PF_ROUTE && + mib[2] == 0 && + (mib[3] == 0 || mib[3] == AF_INET6 || mib[3] == AF_INET) && + mib[4] == NET_RT_IFLIST) + return (0); + } + + /* used by ntpd(8) to read sensors. */ + if (miblen >= 3 && + mib[0] == CTL_HW && mib[1] == HW_SENSORS) + return (0); + + if (miblen == 2 && /* getdomainname() */ + mib[0] == CTL_KERN && mib[1] == KERN_DOMAINNAME) + return (0); + if (miblen == 2 && /* gethostname() */ + mib[0] == CTL_KERN && mib[1] == KERN_HOSTNAME) + return (0); + if (miblen == 2 && /* uname() */ + mib[0] == CTL_KERN && mib[1] == KERN_OSTYPE) + return (0); + if (miblen == 2 && /* uname() */ + mib[0] == CTL_KERN && mib[1] == KERN_OSRELEASE) + return (0); + if (miblen == 2 && /* uname() */ + mib[0] == CTL_KERN && mib[1] == KERN_OSVERSION) + return (0); + if (miblen == 2 && /* uname() */ + mib[0] == CTL_KERN && mib[1] == KERN_VERSION) + return (0); + if (miblen == 2 && /* uname() */ + mib[0] == CTL_HW && mib[1] == HW_MACHINE) + return (0); + if (miblen == 2 && /* getpagesize() */ + mib[0] == CTL_HW && mib[1] == HW_PAGESIZE) + return (0); + if (miblen == 2 && /* setproctitle() */ + mib[0] == CTL_VM && mib[1] == VM_PSSTRINGS) + return (0); + + printf("%s(%d): sysctl %d: %d %d %d %d %d %d\n", + p->p_comm, p->p_pid, miblen, mib[0], mib[1], + mib[2], mib[3], mib[4], mib[5]); + return (EFAULT); +} + +int +pledge_adjtime_check(struct proc *p, const void *v) +{ + const struct timeval *delta = v; + + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + if (delta) + return (EFAULT); + return (0); +} + +int +pledge_connect_check(struct proc *p) +{ + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE)) + return (0); /* A port check happens inside sys_connect() */ + + if ((p->p_p->ps_pledge & (PLEDGE_INET | PLEDGE_UNIX))) + return (0); + return (EPERM); +} + +int +pledge_recvfrom_check(struct proc *p, void *v) +{ + struct sockaddr *from = v; + + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && from == NULL) + return (0); + if (p->p_p->ps_pledge & PLEDGE_INET) + return (0); + if (p->p_p->ps_pledge & PLEDGE_UNIX) + return (0); + if (from == NULL) + return (0); /* behaves just like write */ + return (EPERM); +} + +int +pledge_sendto_check(struct proc *p, const void *v) +{ + const struct sockaddr *to = v; + + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && to == NULL) + return (0); + + if ((p->p_p->ps_pledge & PLEDGE_INET)) + return (0); + if ((p->p_p->ps_pledge & PLEDGE_UNIX)) + return (0); + if (to == NULL) + return (0); /* behaves just like write */ + return (EPERM); +} + +int +pledge_socket_check(struct proc *p, int domain) +{ + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + if ((p->p_p->ps_pledge & (PLEDGE_INET | PLEDGE_UNIX))) + return (0); + if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && + (domain == AF_INET || domain == AF_INET6)) + return (0); + return (EPERM); +} + +int +pledge_bind_check(struct proc *p, const void *v) +{ + + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + if ((p->p_p->ps_pledge & PLEDGE_INET)) + return (0); + return (EPERM); +} + +int +pledge_ioctl_check(struct proc *p, long com, void *v) +{ + struct file *fp = v; + struct vnode *vp = NULL; + + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + /* + * The ioctl's which are always allowed. + */ + switch (com) { + case FIONREAD: + case FIONBIO: + return (0); + } + + if (fp == NULL) + return (EBADF); + vp = (struct vnode *)fp->f_data; + + /* + * Further sets of ioctl become available, but are checked a + * bit more carefully against the vnode. + */ + if ((p->p_p->ps_pledge & PLEDGE_IOCTL)) { + switch (com) { + case FIOCLEX: + case FIONCLEX: + case FIOASYNC: + case FIOSETOWN: + case FIOGETOWN: + return (0); + case TIOCGETA: + case TIOCGPGRP: + case TIOCGWINSZ: /* various programs */ + if (fp->f_type == DTYPE_VNODE && (vp->v_flag & VISTTY)) + return (0); + break; + case BIOCGSTATS: /* bpf: tcpdump privsep on ^C */ + if (fp->f_type == DTYPE_VNODE && + fp->f_ops->fo_ioctl == vn_ioctl) + return (0); + break; + case MTIOCGET: + case MTIOCTOP: + /* for pax(1) and such, checking tapes... */ + if (fp->f_type == DTYPE_VNODE && + (vp->v_type == VCHR || vp->v_type == VBLK)) + return (0); + break; + case SIOCGIFGROUP: + if ((p->p_p->ps_pledge & PLEDGE_INET) && + fp->f_type == DTYPE_SOCKET) + return (0); + break; + } + } + + if ((p->p_p->ps_pledge & PLEDGE_ROUTE)) { + switch (com) { + case SIOCGIFADDR: + case SIOCGIFFLAGS: + case SIOCGIFRDOMAIN: + if (fp->f_type == DTYPE_SOCKET) + return (0); + break; + } + } + + if ((p->p_p->ps_pledge & PLEDGE_TTY)) { + switch (com) { + case TIOCSPGRP: + if ((p->p_p->ps_pledge & PLEDGE_PROC) == 0) + break; + /* FALTHROUGH */ + case TIOCGETA: + case TIOCGPGRP: + case TIOCGWINSZ: /* various programs */ +#if notyet + case TIOCSTI: /* ksh? csh? */ +#endif + case TIOCSBRK: /* cu */ + case TIOCCDTR: /* cu */ + case TIOCSETA: /* cu, ... */ + case TIOCSETAW: /* cu, ... */ + case TIOCSETAF: /* tcsetattr TCSAFLUSH, script */ + if (fp->f_type == DTYPE_VNODE && (vp->v_flag & VISTTY)) + return (0); + break; + } + } + + return pledge_fail(p, EPERM, PLEDGE_IOCTL); +} + +int +pledge_setsockopt_check(struct proc *p, int level, int optname) +{ + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + /* common case for PLEDGE_UNIX and PLEDGE_INET */ + switch (level) { + case SOL_SOCKET: + switch (optname) { + case SO_RTABLE: + return (EPERM); + } + return (0); + } + + if ((p->p_p->ps_pledge & PLEDGE_INET) == 0) + return (EPERM); + + switch (level) { + case IPPROTO_TCP: + switch (optname) { + case TCP_NODELAY: + case TCP_MD5SIG: + case TCP_SACK_ENABLE: + case TCP_MAXSEG: + case TCP_NOPUSH: + return (0); + } + break; + case IPPROTO_IP: + switch (optname) { + case IP_TOS: + case IP_TTL: + case IP_MINTTL: + case IP_PORTRANGE: + case IP_RECVDSTADDR: + return (0); + case IP_MULTICAST_IF: + case IP_ADD_MEMBERSHIP: + case IP_DROP_MEMBERSHIP: + if ((p->p_p->ps_pledge & PLEDGE_MCAST) == 0) + return (0); + break; + } + break; + case IPPROTO_ICMP: + break; + case IPPROTO_IPV6: + switch (optname) { + case IPV6_UNICAST_HOPS: + case IPV6_RECVHOPLIMIT: + case IPV6_PORTRANGE: + case IPV6_RECVPKTINFO: +#ifdef notyet + case IPV6_V6ONLY: +#endif + return (0); + case IPV6_MULTICAST_IF: + case IPV6_JOIN_GROUP: + case IPV6_LEAVE_GROUP: + if ((p->p_p->ps_pledge & PLEDGE_MCAST) == 0) + return (0); + break; + } + break; + case IPPROTO_ICMPV6: + break; + } + return (EPERM); +} + +int +pledge_dns_check(struct proc *p, in_port_t port) +{ + if ((p->p_p->ps_flags & PS_PLEDGE) == 0) + return (0); + + if ((p->p_p->ps_pledge & PLEDGE_INET)) + return (0); + if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && port == htons(53)) + return (0); /* Allow a DNS connect outbound */ + return (EPERM); +} + +void +pledge_dropwpaths(struct process *pr) +{ + if (pr->ps_pledgepaths && --pr->ps_pledgepaths->wl_ref == 0) { + struct whitepaths *wl = pr->ps_pledgepaths; + int i; + + for (i = 0; i < wl->wl_count; i++) + free(wl->wl_paths[i].name, M_TEMP, wl->wl_paths[i].len); + free(wl, M_TEMP, wl->wl_size); + } + pr->ps_pledgepaths = NULL; +} + +int +canonpath(const char *input, char *buf, size_t bufsize) +{ + char *p, *q, *s, *end; + + /* can't canon relative paths, don't bother */ + if (input[0] != '/') { + if (strlcpy(buf, input, bufsize) >= bufsize) + return (ENAMETOOLONG); + return (0); + } + + /* easiest to work with strings always ending in '/' */ + if (snprintf(buf, bufsize, "%s/", input) >= bufsize) + return (ENAMETOOLONG); + + /* after this we will only be shortening the string. */ + p = buf; + q = p; + while (*p) { + if (p[0] == '/' && p[1] == '/') { + p += 1; + } else if (p[0] == '/' && p[1] == '.' && + p[2] == '/') { + p += 2; + } else { + *q++ = *p++; + } + } + *q = 0; + + end = buf + strlen(buf); + s = buf; + p = s; + while (1) { + /* find "/../" (where's strstr when you need it?) */ + while (p < end) { + if (p[0] == '/' && strncmp(p + 1, "../", 3) == 0) + break; + p++; + } + if (p == end) + break; + if (p == s) { + memmove(s, p + 3, end - p - 3 + 1); + end -= 3; + } else { + /* s starts with '/', so we know there's one + * somewhere before p. */ + q = p - 1; + while (*q != '/') + q--; + memmove(q, p + 3, end - p - 3 + 1); + end -= p + 3 - q; + p = q; + } + } + if (end > s + 1) + *(end - 1) = 0; /* remove trailing '/' */ + + return 0; +} diff --git a/sys/kern/kern_tame.c b/sys/kern/kern_tame.c deleted file mode 100644 index 3ccb09989f8..00000000000 --- a/sys/kern/kern_tame.c +++ /dev/null @@ -1,1241 +0,0 @@ -/* $OpenBSD: kern_tame.c,v 1.71 2015/10/09 01:10:27 deraadt Exp $ */ - -/* - * Copyright (c) 2015 Nicholas Marriott - * Copyright (c) 2015 Theo de Raadt - * - * Permission to use, copy, modify, and distribute this software for any - * purpose with or without fee is hereby granted, provided that the above - * copyright notice and this permission notice appear in all copies. - * - * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES - * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF - * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR - * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES - * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN - * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF - * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. - */ - -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include - -int canonpath(const char *input, char *buf, size_t bufsize); - -const u_int pledge_syscalls[SYS_MAXSYSCALL] = { - [SYS_exit] = 0xffffffff, - [SYS_kbind] = 0xffffffff, - - [SYS_getuid] = PLEDGE_SELF, - [SYS_geteuid] = PLEDGE_SELF, - [SYS_getresuid] = PLEDGE_SELF, - [SYS_getgid] = PLEDGE_SELF, - [SYS_getegid] = PLEDGE_SELF, - [SYS_getresgid] = PLEDGE_SELF, - [SYS_getgroups] = PLEDGE_SELF, - [SYS_getlogin] = PLEDGE_SELF, - [SYS_getpgrp] = PLEDGE_SELF, - [SYS_getpgid] = PLEDGE_SELF, - [SYS_getppid] = PLEDGE_SELF, - [SYS_getsid] = PLEDGE_SELF, - [SYS_getthrid] = PLEDGE_SELF, - [SYS_getrlimit] = PLEDGE_SELF, - [SYS_gettimeofday] = PLEDGE_SELF, - [SYS_getdtablecount] = PLEDGE_SELF, - [SYS_getrusage] = PLEDGE_SELF, - [SYS_issetugid] = PLEDGE_SELF, - [SYS_clock_getres] = PLEDGE_SELF, - [SYS_clock_gettime] = PLEDGE_SELF, - [SYS_getpid] = PLEDGE_SELF, - [SYS_umask] = PLEDGE_SELF, - [SYS_sysctl] = PLEDGE_SELF, /* read-only; narrow subset */ - [SYS_adjtime] = PLEDGE_SELF, /* read-only */ - - [SYS_fchdir] = PLEDGE_SELF, /* careful of directory fd inside jails */ - - /* needed by threaded programs */ - [SYS_sched_yield] = PLEDGE_SELF, - [SYS___thrsleep] = PLEDGE_SELF, - [SYS___thrwakeup] = PLEDGE_SELF, - [SYS___threxit] = PLEDGE_SELF, - [SYS___thrsigdivert] = PLEDGE_SELF, - - [SYS_sendsyslog] = PLEDGE_SELF, - [SYS_nanosleep] = PLEDGE_SELF, - [SYS_sigprocmask] = PLEDGE_SELF, - [SYS_sigaction] = PLEDGE_SELF, - [SYS_sigreturn] = PLEDGE_SELF, - [SYS_sigpending] = PLEDGE_SELF, - [SYS_getitimer] = PLEDGE_SELF, - [SYS_setitimer] = PLEDGE_SELF, - - [SYS_pledge] = PLEDGE_SELF, - - [SYS_wait4] = PLEDGE_SELF, - - [SYS_poll] = PLEDGE_RW, - [SYS_kevent] = PLEDGE_RW, - [SYS_kqueue] = PLEDGE_RW, - [SYS_select] = PLEDGE_RW, - - [SYS_close] = PLEDGE_RW, - [SYS_dup] = PLEDGE_RW, - [SYS_dup2] = PLEDGE_RW, - [SYS_dup3] = PLEDGE_RW, - [SYS_closefrom] = PLEDGE_RW, - [SYS_shutdown] = PLEDGE_RW, - [SYS_read] = PLEDGE_RW, - [SYS_readv] = PLEDGE_RW, - [SYS_pread] = PLEDGE_RW, - [SYS_preadv] = PLEDGE_RW, - [SYS_write] = PLEDGE_RW, - [SYS_writev] = PLEDGE_RW, - [SYS_pwrite] = PLEDGE_RW, - [SYS_pwritev] = PLEDGE_RW, - [SYS_ftruncate] = PLEDGE_RW, - [SYS_lseek] = PLEDGE_RW, - [SYS_fstat] = PLEDGE_RW, - - [SYS_fcntl] = PLEDGE_RW, - [SYS_fsync] = PLEDGE_RW, - [SYS_pipe] = PLEDGE_RW, - [SYS_pipe2] = PLEDGE_RW, - [SYS_socketpair] = PLEDGE_RW, - [SYS_getdents] = PLEDGE_RW, - - [SYS_sendto] = PLEDGE_RW | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, - [SYS_sendmsg] = PLEDGE_RW, - [SYS_recvmsg] = PLEDGE_RW, - [SYS_recvfrom] = PLEDGE_RW | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, - - [SYS_fork] = PLEDGE_PROC, - [SYS_vfork] = PLEDGE_PROC, - [SYS_kill] = PLEDGE_PROC, - [SYS_setpgid] = PLEDGE_PROC, - [SYS_sigsuspend] = PLEDGE_PROC, - [SYS_setrlimit] = PLEDGE_PROC, - - [SYS_execve] = PLEDGE_EXEC, - - [SYS_setgroups] = PLEDGE_PROC, - [SYS_setresgid] = PLEDGE_PROC, - [SYS_setresuid] = PLEDGE_PROC, - - /* FIONREAD/FIONBIO, plus further checks in pledge_ioctl_check() */ - [SYS_ioctl] = PLEDGE_RW | PLEDGE_IOCTL | PLEDGE_TTY, - - [SYS_getentropy] = PLEDGE_MALLOC, - [SYS_madvise] = PLEDGE_MALLOC, - [SYS_minherit] = PLEDGE_MALLOC, - [SYS_mmap] = PLEDGE_MALLOC, - [SYS_mprotect] = PLEDGE_MALLOC, - [SYS_mquery] = PLEDGE_MALLOC, - [SYS_munmap] = PLEDGE_MALLOC, - - [SYS_open] = PLEDGE_SELF, /* further checks in namei */ - [SYS_stat] = PLEDGE_SELF, /* further checks in namei */ - [SYS_access] = PLEDGE_SELF, /* further checks in namei */ - [SYS_readlink] = PLEDGE_SELF, /* further checks in namei */ - - [SYS_chdir] = PLEDGE_RPATH, - [SYS_openat] = PLEDGE_RPATH | PLEDGE_WPATH, - [SYS_fstatat] = PLEDGE_RPATH | PLEDGE_WPATH, - [SYS_faccessat] = PLEDGE_RPATH | PLEDGE_WPATH, - [SYS_readlinkat] = PLEDGE_RPATH | PLEDGE_WPATH, - [SYS_lstat] = PLEDGE_RPATH | PLEDGE_WPATH | PLEDGE_TMPPATH, - [SYS_rename] = PLEDGE_CPATH, - [SYS_rmdir] = PLEDGE_CPATH, - [SYS_renameat] = PLEDGE_CPATH, - [SYS_link] = PLEDGE_CPATH, - [SYS_linkat] = PLEDGE_CPATH, - [SYS_symlink] = PLEDGE_CPATH, - [SYS_unlink] = PLEDGE_CPATH | PLEDGE_TMPPATH, - [SYS_unlinkat] = PLEDGE_CPATH, - [SYS_mkdir] = PLEDGE_CPATH, - [SYS_mkdirat] = PLEDGE_CPATH, - - /* - * Classify as RPATH|WPATH, because of path information leakage. - * WPATH due to unknown use of mk*temp(3) on non-/tmp paths.. - */ - [SYS___getcwd] = PLEDGE_RPATH | PLEDGE_WPATH, - - /* Classify as RPATH, because these leak path information */ - [SYS_getfsstat] = PLEDGE_RPATH, - [SYS_statfs] = PLEDGE_RPATH, - [SYS_fstatfs] = PLEDGE_RPATH, - - [SYS_utimes] = PLEDGE_FATTR, - [SYS_futimes] = PLEDGE_FATTR, - [SYS_utimensat] = PLEDGE_FATTR, - [SYS_futimens] = PLEDGE_FATTR, - [SYS_chmod] = PLEDGE_FATTR, - [SYS_fchmod] = PLEDGE_FATTR, - [SYS_fchmodat] = PLEDGE_FATTR, - [SYS_chflags] = PLEDGE_FATTR, - [SYS_chflagsat] = PLEDGE_FATTR, - [SYS_chown] = PLEDGE_FATTR, - [SYS_fchownat] = PLEDGE_FATTR, - [SYS_lchown] = PLEDGE_FATTR, - [SYS_fchown] = PLEDGE_FATTR, - - [SYS_socket] = PLEDGE_INET | PLEDGE_UNIX | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, - [SYS_connect] = PLEDGE_INET | PLEDGE_UNIX | PLEDGE_DNS_ACTIVE | PLEDGE_YP_ACTIVE, - - [SYS_listen] = PLEDGE_INET | PLEDGE_UNIX, - [SYS_bind] = PLEDGE_INET | PLEDGE_UNIX, - [SYS_accept4] = PLEDGE_INET | PLEDGE_UNIX, - [SYS_accept] = PLEDGE_INET | PLEDGE_UNIX, - [SYS_getpeername] = PLEDGE_INET | PLEDGE_UNIX, - [SYS_getsockname] = PLEDGE_INET | PLEDGE_UNIX, - [SYS_setsockopt] = PLEDGE_INET | PLEDGE_UNIX, - [SYS_getsockopt] = PLEDGE_INET | PLEDGE_UNIX, - - [SYS_flock] = PLEDGE_GETPW, -}; - -static const struct { - char *name; - int flags; -} pledgereq[] = { - { "malloc", PLEDGE_SELF | PLEDGE_MALLOC }, - { "rw", PLEDGE_SELF | PLEDGE_RW }, - { "stdio", PLEDGE_SELF | PLEDGE_MALLOC | PLEDGE_RW }, - { "rpath", PLEDGE_SELF | PLEDGE_RW | PLEDGE_RPATH }, - { "wpath", PLEDGE_SELF | PLEDGE_RW | PLEDGE_WPATH }, - { "tmppath", PLEDGE_SELF | PLEDGE_RW | PLEDGE_TMPPATH }, - { "inet", PLEDGE_SELF | PLEDGE_RW | PLEDGE_INET }, - { "unix", PLEDGE_SELF | PLEDGE_RW | PLEDGE_UNIX }, - { "dns", PLEDGE_SELF | PLEDGE_MALLOC | PLEDGE_DNSPATH }, - { "getpw", PLEDGE_SELF | PLEDGE_MALLOC | PLEDGE_RW | PLEDGE_GETPW }, -/*X*/ { "cmsg", PLEDGE_UNIX | PLEDGE_INET | PLEDGE_SENDFD | PLEDGE_RECVFD }, - { "sendfd", PLEDGE_RW | PLEDGE_SENDFD }, - { "recvfd", PLEDGE_RW | PLEDGE_RECVFD }, - { "ioctl", PLEDGE_IOCTL }, - { "route", PLEDGE_ROUTE }, - { "mcast", PLEDGE_MCAST }, - { "tty", PLEDGE_TTY }, - { "proc", PLEDGE_PROC }, - { "exec", PLEDGE_EXEC }, - { "cpath", PLEDGE_CPATH }, - { "abort", PLEDGE_ABORT }, - { "fattr", PLEDGE_FATTR }, - { "prot_exec", PLEDGE_PROTEXEC }, -}; - -int -sys_pledge(struct proc *p, void *v, register_t *retval) -{ - struct sys_pledge_args /* { - syscallarg(const char *)request; - syscallarg(const char **)paths; - } */ *uap = v; - int flags = 0; - int error; - - if (SCARG(uap, request)) { - size_t rbuflen; - char *rbuf, *rp, *pn; - int f, i; - - rbuf = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); - error = copyinstr(SCARG(uap, request), rbuf, MAXPATHLEN, - &rbuflen); - if (error) { - free(rbuf, M_TEMP, MAXPATHLEN); - return (error); - } -#ifdef KTRACE - if (KTRPOINT(p, KTR_STRUCT)) - ktrstruct(p, "pledgereq", rbuf, rbuflen-1); -#endif - - for (rp = rbuf; rp && *rp && error == 0; rp = pn) { - pn = strchr(rp, ' '); /* find terminator */ - if (pn) { - while (*pn == ' ') - *pn++ = '\0'; - } - - for (f = i = 0; i < nitems(pledgereq); i++) { - if (strcmp(rp, pledgereq[i].name) == 0) { - f = pledgereq[i].flags; - break; - } - } - if (f == 0) { - free(rbuf, M_TEMP, MAXPATHLEN); - return (EINVAL); - } - flags |= f; - } - free(rbuf, M_TEMP, MAXPATHLEN); - } - - if (flags & ~PLEDGE_USERSET) - return (EINVAL); - - if ((p->p_p->ps_flags & PS_PLEDGE)) { - /* Already pledged, only allow reductions */ - if (((flags | p->p_p->ps_pledge) & PLEDGE_USERSET) != - (p->p_p->ps_pledge & PLEDGE_USERSET)) { - return (EPERM); - } - - flags &= p->p_p->ps_pledge; - flags &= PLEDGE_USERSET; /* Relearn _ACTIVE */ - } - - if (SCARG(uap, paths)) { - const char **u = SCARG(uap, paths), *sp; - struct whitepaths *wl; - char *cwdpath = NULL, *path; - size_t cwdpathlen = MAXPATHLEN * 4, cwdlen, len, maxargs = 0; - int i, error; - - if (p->p_p->ps_pledgepaths) - return (EPERM); - - /* Count paths */ - for (i = 0; i < PLEDGE_MAXPATHS; i++) { - if ((error = copyin(u + i, &sp, sizeof(sp))) != 0) - return (error); - if (sp == NULL) - break; - } - if (i == PLEDGE_MAXPATHS) - return (E2BIG); - - wl = malloc(sizeof *wl + sizeof(struct whitepath) * (i+1), - M_TEMP, M_WAITOK | M_ZERO); - wl->wl_size = sizeof *wl + sizeof(struct whitepath) * (i+1); - wl->wl_count = i; - wl->wl_ref = 1; - - path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); - - /* Copy in */ - for (i = 0; i < wl->wl_count; i++) { - char *fullpath = NULL, *builtpath = NULL, *canopath = NULL, *cwd; - size_t builtlen = 0; - - if ((error = copyin(u + i, &sp, sizeof(sp))) != 0) - break; - if (sp == NULL) - break; - if ((error = copyinstr(sp, path, MAXPATHLEN, &len)) != 0) - break; -#ifdef KTRACE - if (KTRPOINT(p, KTR_STRUCT)) - ktrstruct(p, "pledgepath", path, len-1); -#endif - - /* If path is relative, prepend cwd */ - if (path[0] != '/') { - if (cwdpath == NULL) { - char *bp, *bpend; - - cwdpath = malloc(cwdpathlen, M_TEMP, M_WAITOK); - bp = &cwdpath[cwdpathlen]; - bpend = bp; - *(--bp) = '\0'; - - error = vfs_getcwd_common(p->p_fd->fd_cdir, - NULL, &bp, cwdpath, cwdpathlen/2, - GETCWD_CHECK_ACCESS, p); - if (error) - break; - cwd = bp; - cwdlen = (bpend - bp); - } - - /* NUL included in cwd component */ - builtlen = cwdlen + 1 + strlen(path); - if (builtlen > PATH_MAX) { - error = ENAMETOOLONG; - break; - } - builtpath = malloc(builtlen, M_TEMP, M_WAITOK); - snprintf(builtpath, builtlen, "%s/%s", cwd, path); - // printf("pledge: builtpath = %s\n", builtpath); - fullpath = builtpath; - } else - fullpath = path; - - canopath = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); - error = canonpath(fullpath, canopath, MAXPATHLEN); - - free(builtpath, M_TEMP, builtlen); - if (error != 0) { - free(canopath, M_TEMP, MAXPATHLEN); - break; - } - - len = strlen(canopath) + 1; - - //printf("pledge: canopath = %s %lld strlen %lld\n", canopath, - // (long long)len, (long long)strlen(canopath)); - - if (maxargs += len > ARG_MAX) { - error = E2BIG; - break; - } - wl->wl_paths[i].name = malloc(len, M_TEMP, M_WAITOK); - memcpy(wl->wl_paths[i].name, canopath, len); - wl->wl_paths[i].len = len; - free(canopath, M_TEMP, MAXPATHLEN); - } - free(path, M_TEMP, MAXPATHLEN); - free(cwdpath, M_TEMP, cwdpathlen); - - if (error) { - for (i = 0; i < wl->wl_count; i++) - free(wl->wl_paths[i].name, - M_TEMP, wl->wl_paths[i].len); - free(wl, M_TEMP, wl->wl_size); - return (error); - } - p->p_p->ps_pledgepaths = wl; -#if 0 - printf("pledge: %s(%d): paths loaded:\n", p->p_comm, p->p_pid); - for (i = 0; i < wl->wl_count; i++) - if (wl->wl_paths[i].name) - printf("pledge: %d=%s %lld\n", i, wl->wl_paths[i].name, - (long long)wl->wl_paths[i].len); -#endif - } - - p->p_p->ps_pledge = flags; - p->p_p->ps_flags |= PS_PLEDGE; - return (0); -} - -int -pledge_check(struct proc *p, int code) -{ - p->p_pledgenote = p->p_pledgeafter = 0; /* XX optimise? */ - p->p_pledge_syscall = code; - - if (code < 0 || code > SYS_MAXSYSCALL - 1) - return (0); - - if (p->p_p->ps_pledge == 0) - return (code == SYS_exit || code == SYS_kbind); - return (p->p_p->ps_pledge & pledge_syscalls[code]); -} - -int -pledge_fail(struct proc *p, int error, int code) -{ - printf("%s(%d): syscall %d\n", p->p_comm, p->p_pid, p->p_pledge_syscall); - if (p->p_p->ps_pledge & PLEDGE_ABORT) { /* Core dump requested */ - struct sigaction sa; - - memset(&sa, 0, sizeof sa); - sa.sa_handler = SIG_DFL; - setsigvec(p, SIGABRT, &sa); - psignal(p, SIGABRT); - } else - psignal(p, SIGKILL); - - p->p_p->ps_pledge = 0; /* Disable all PLEDGE_ flags */ - return (error); -} - -/* - * Need to make it more obvious that one cannot get through here - * without the right flags set - */ -int -pledge_namei(struct proc *p, char *origpath) -{ - char path[PATH_MAX]; - - if (p->p_pledgenote == TMN_COREDUMP) - return (0); /* Allow a coredump */ - - if (canonpath(origpath, path, sizeof(path)) != 0) - return (pledge_fail(p, EPERM, PLEDGE_RPATH)); - - if ((p->p_pledgenote & TMN_FATTR) && - (p->p_p->ps_pledge & PLEDGE_FATTR) == 0) { - printf("%s(%d): inode syscall%d, not allowed\n", - p->p_comm, p->p_pid, p->p_pledge_syscall); - return (pledge_fail(p, EPERM, PLEDGE_FATTR)); - } - - /* Detect what looks like a mkstemp(3) family operation */ - if ((p->p_p->ps_pledge & PLEDGE_TMPPATH) && - (p->p_pledge_syscall == SYS_open) && - (p->p_pledgenote & TMN_CPATH) && - strncmp(path, "/tmp/", sizeof("/tmp/") - 1) == 0) { - return (0); - } - - /* Allow unlinking of a mkstemp(3) file... - * Good opportunity for strict checks here. - */ - if ((p->p_p->ps_pledge & PLEDGE_TMPPATH) && - (p->p_pledge_syscall == SYS_unlink) && - strncmp(path, "/tmp/", sizeof("/tmp/") - 1) == 0) { - return (0); - } - - /* open, mkdir, or other path creation operation */ - if ((p->p_pledgenote & TMN_CPATH) && - ((p->p_p->ps_pledge & PLEDGE_CPATH) == 0)) - return (pledge_fail(p, EPERM, PLEDGE_CPATH)); - - if ((p->p_pledgenote & TMN_WPATH) && - (p->p_p->ps_pledge & PLEDGE_WPATH) == 0) - return (pledge_fail(p, EPERM, PLEDGE_WPATH)); - - /* Read-only paths used occasionally by libc */ - switch (p->p_pledge_syscall) { - case SYS_access: - /* tzset() needs this. */ - if ((p->p_pledgenote == TMN_RPATH) && - strcmp(path, "/etc/localtime") == 0) - return (0); - break; - case SYS_open: - /* getpw* and friends need a few files */ - if ((p->p_pledgenote == TMN_RPATH) && - (p->p_p->ps_pledge & PLEDGE_GETPW)) { - if (strcmp(path, "/etc/spwd.db") == 0) - return (EPERM); - if (strcmp(path, "/etc/pwd.db") == 0) - return (0); - if (strcmp(path, "/etc/group") == 0) - return (0); - } - - /* DNS needs /etc/{resolv.conf,hosts,services}. */ - if ((p->p_pledgenote == TMN_RPATH) && - (p->p_p->ps_pledge & PLEDGE_DNSPATH)) { - if (strcmp(path, "/etc/resolv.conf") == 0) { - p->p_pledgeafter |= TMA_DNSRESOLV; - return (0); - } - if (strcmp(path, "/etc/hosts") == 0) - return (0); - if (strcmp(path, "/etc/services") == 0) - return (0); - } - if ((p->p_pledgenote == TMN_RPATH) && - (p->p_p->ps_pledge & PLEDGE_GETPW)) { - if (strcmp(path, "/var/run/ypbind.lock") == 0) { - p->p_pledgeafter |= TMA_YPLOCK; - return (0); - } - if (strncmp(path, "/var/yp/binding/", - sizeof("/var/yp/binding/") - 1) == 0) - return (0); - } - /* tzset() needs these. */ - if ((p->p_pledgenote == TMN_RPATH) && - strncmp(path, "/usr/share/zoneinfo/", - sizeof("/usr/share/zoneinfo/") - 1) == 0) - return (0); - if ((p->p_pledgenote == TMN_RPATH) && - strcmp(path, "/etc/localtime") == 0) - return (0); - - /* /usr/share/nls/../libc.cat has to succeed for strerror(3). */ - if ((p->p_pledgenote == TMN_RPATH) && - strncmp(path, "/usr/share/nls/", - sizeof("/usr/share/nls/") - 1) == 0 && - strcmp(path + strlen(path) - 9, "/libc.cat") == 0) - return (0); - break; - case SYS_readlink: - /* Allow /etc/malloc.conf for malloc(3). */ - if ((p->p_pledgenote == TMN_RPATH) && - strcmp(path, "/etc/malloc.conf") == 0) - return (0); - break; - case SYS_stat: - /* DNS needs /etc/resolv.conf. */ - if ((p->p_pledgenote == TMN_RPATH) && - (p->p_p->ps_pledge & PLEDGE_DNSPATH)) { - if (strcmp(path, "/etc/resolv.conf") == 0) { - p->p_pledgeafter |= TMA_DNSRESOLV; - return (0); - } - } - break; - } - - /* - * If a whitelist is set, compare canonical paths. Anything - * not on the whitelist gets ENOENT. - */ - if (p->p_p->ps_pledgepaths) { - struct whitepaths *wl = p->p_p->ps_pledgepaths; - char *fullpath, *builtpath = NULL, *canopath = NULL; - size_t builtlen = 0; - int i, error; - - if (origpath[0] != '/') { - char *cwdpath, *cwd, *bp, *bpend; - size_t cwdpathlen = MAXPATHLEN * 4, cwdlen; - - cwdpath = malloc(cwdpathlen, M_TEMP, M_WAITOK); - bp = &cwdpath[cwdpathlen]; - bpend = bp; - *(--bp) = '\0'; - - error = vfs_getcwd_common(p->p_fd->fd_cdir, - NULL, &bp, cwdpath, cwdpathlen/2, - GETCWD_CHECK_ACCESS, p); - if (error) { - free(cwdpath, M_TEMP, cwdpathlen); - return (error); - } - cwd = bp; - cwdlen = (bpend - bp); - - /* NUL included in cwd component */ - builtlen = cwdlen + 1 + strlen(origpath); - builtpath = malloc(builtlen, M_TEMP, M_WAITOK); - snprintf(builtpath, builtlen, "%s/%s", cwd, origpath); - fullpath = builtpath; - free(cwdpath, M_TEMP, cwdpathlen); - - //printf("namei: builtpath = %s %lld strlen %lld\n", builtpath, - // (long long)builtlen, (long long)strlen(builtpath)); - } else - fullpath = path; - - canopath = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); - error = canonpath(fullpath, canopath, MAXPATHLEN); - - free(builtpath, M_TEMP, builtlen); - if (error != 0) { - free(canopath, M_TEMP, MAXPATHLEN); - return (pledge_fail(p, EPERM, PLEDGE_RPATH)); - } - - //printf("namei: canopath = %s strlen %lld\n", canopath, - // (long long)strlen(canopath)); - - error = ENOENT; - for (i = 0; i < wl->wl_count && wl->wl_paths[i].name && error; i++) { - if (strncmp(canopath, wl->wl_paths[i].name, - wl->wl_paths[i].len - 1) == 0) { - u_char term = canopath[wl->wl_paths[i].len - 1]; - - if (term == '\0' || term == '/' || - wl->wl_paths[i].name[1] == '\0') - error = 0; - } - } - free(canopath, M_TEMP, MAXPATHLEN); - return (error); /* Don't hint why it failed */ - } - - if (p->p_p->ps_pledge & PLEDGE_RPATH) - return (0); - if (p->p_p->ps_pledge & PLEDGE_WPATH) - return (0); - if (p->p_p->ps_pledge & PLEDGE_CPATH) - return (0); - - return (pledge_fail(p, EPERM, PLEDGE_RPATH)); -} - -void -pledge_aftersyscall(struct proc *p, int code, int error) -{ - if ((p->p_pledgeafter & TMA_YPLOCK) && error == 0) - atomic_setbits_int(&p->p_p->ps_pledge, PLEDGE_YP_ACTIVE | PLEDGE_INET); - if ((p->p_pledgeafter & TMA_DNSRESOLV) && error == 0) - atomic_setbits_int(&p->p_p->ps_pledge, PLEDGE_DNS_ACTIVE); -} - -/* - * By default, only the advisory cmsg's can be received from the kernel, - * such as TIMESTAMP ntpd. - * - * If PLEDGE_RECVFD is set SCM_RIGHTS is also allowed in for a carefully - * selected set of descriptors (specifically to exclude directories). - * - * This results in a kill upon recv, if some other process on the system - * send a SCM_RIGHTS to an open socket of some sort. That will discourage - * leaving such sockets lying around... - */ -int -pledge_cmsg_recv(struct proc *p, struct mbuf *control) -{ - struct msghdr tmp; - struct cmsghdr *cmsg; - int *fdp, fd; - struct file *fp; - int nfds, i; - - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - /* Scan the cmsg */ - memset(&tmp, 0, sizeof(tmp)); - tmp.msg_control = mtod(control, struct cmsghdr *); - tmp.msg_controllen = control->m_len; - cmsg = CMSG_FIRSTHDR(&tmp); - - while (cmsg != NULL) { - if (cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS) - break; - cmsg = CMSG_NXTHDR(&tmp, cmsg); - } - - /* No SCM_RIGHTS found -> OK */ - if (cmsg == NULL) - return (0); - - if ((p->p_p->ps_pledge & PLEDGE_RECVFD) == 0) - return pledge_fail(p, EPERM, PLEDGE_RECVFD); - - /* In OpenBSD, a CMSG only contains one SCM_RIGHTS. Check it. */ - fdp = (int *)CMSG_DATA(cmsg); - nfds = (cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg))) / - sizeof(struct file *); - for (i = 0; i < nfds; i++) { - struct vnode *vp; - - fd = *fdp++; - fp = fd_getfile(p->p_fd, fd); - if (fp == NULL) - return pledge_fail(p, EBADF, PLEDGE_RECVFD); - - /* Only allow passing of sockets, pipes, and pure files */ - switch (fp->f_type) { - case DTYPE_SOCKET: - case DTYPE_PIPE: - continue; - case DTYPE_VNODE: - vp = (struct vnode *)fp->f_data; - if (vp->v_type == VREG) - continue; - break; - default: - break; - } - return pledge_fail(p, EPERM, PLEDGE_RECVFD); - } - return (0); -} - -/* - * When pledged, default prevents sending of a cmsg. - * - * Unlike pledge_cmsg_recv pledge_cmsg_send is called with individual - * cmsgs one per mbuf. So no need to loop or scan. - */ -int -pledge_cmsg_send(struct proc *p, struct mbuf *control) -{ - struct cmsghdr *cmsg; - int *fdp, fd; - struct file *fp; - int nfds, i; - - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - if ((p->p_p->ps_pledge & PLEDGE_SENDFD) == 0) - return pledge_fail(p, EPERM, PLEDGE_SENDFD); - - /* Scan the cmsg */ - cmsg = mtod(control, struct cmsghdr *); - - /* Contains no SCM_RIGHTS, so OK */ - if (!(cmsg->cmsg_level == SOL_SOCKET && - cmsg->cmsg_type == SCM_RIGHTS)) - return (0); - - /* In OpenBSD, a CMSG only contains one SCM_RIGHTS. Check it. */ - fdp = (int *)CMSG_DATA(cmsg); - nfds = (cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg))) / - sizeof(struct file *); - for (i = 0; i < nfds; i++) { - struct vnode *vp; - - fd = *fdp++; - fp = fd_getfile(p->p_fd, fd); - if (fp == NULL) - return pledge_fail(p, EBADF, PLEDGE_SENDFD); - - /* Only allow passing of sockets, pipes, and pure files */ - switch (fp->f_type) { - case DTYPE_SOCKET: - case DTYPE_PIPE: - continue; - case DTYPE_VNODE: - vp = (struct vnode *)fp->f_data; - if (vp->v_type == VREG) - continue; - break; - default: - break; - } - /* Not allowed to send a bad fd type */ - return pledge_fail(p, EPERM, PLEDGE_SENDFD); - } - return (0); -} - -int -pledge_sysctl_check(struct proc *p, int miblen, int *mib, void *new) -{ - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - if (new) - return (EFAULT); - - /* routing table observation */ - if ((p->p_p->ps_pledge & PLEDGE_ROUTE)) { - if (miblen == 7 && - mib[0] == CTL_NET && mib[1] == PF_ROUTE && - mib[2] == 0 && - (mib[3] == 0 || mib[3] == AF_INET6 || mib[3] == AF_INET) && - mib[4] == NET_RT_DUMP) - return (0); - - if (miblen == 6 && - mib[0] == CTL_NET && mib[1] == PF_ROUTE && - mib[2] == 0 && - (mib[3] == 0 || mib[3] == AF_INET6 || mib[3] == AF_INET) && - mib[4] == NET_RT_TABLE) - return (0); - - if (miblen == 7 && /* exposes MACs */ - mib[0] == CTL_NET && mib[1] == PF_ROUTE && - mib[2] == 0 && mib[3] == AF_INET && - mib[4] == NET_RT_FLAGS && mib[5] == RTF_LLINFO) - return (0); - } - - if ((p->p_p->ps_pledge & (PLEDGE_ROUTE | PLEDGE_INET))) { - if (miblen == 6 && /* getifaddrs() */ - mib[0] == CTL_NET && mib[1] == PF_ROUTE && - mib[2] == 0 && - (mib[3] == 0 || mib[3] == AF_INET6 || mib[3] == AF_INET) && - mib[4] == NET_RT_IFLIST) - return (0); - } - - /* used by ntpd(8) to read sensors. */ - if (miblen >= 3 && - mib[0] == CTL_HW && mib[1] == HW_SENSORS) - return (0); - - if (miblen == 2 && /* getdomainname() */ - mib[0] == CTL_KERN && mib[1] == KERN_DOMAINNAME) - return (0); - if (miblen == 2 && /* gethostname() */ - mib[0] == CTL_KERN && mib[1] == KERN_HOSTNAME) - return (0); - if (miblen == 2 && /* uname() */ - mib[0] == CTL_KERN && mib[1] == KERN_OSTYPE) - return (0); - if (miblen == 2 && /* uname() */ - mib[0] == CTL_KERN && mib[1] == KERN_OSRELEASE) - return (0); - if (miblen == 2 && /* uname() */ - mib[0] == CTL_KERN && mib[1] == KERN_OSVERSION) - return (0); - if (miblen == 2 && /* uname() */ - mib[0] == CTL_KERN && mib[1] == KERN_VERSION) - return (0); - if (miblen == 2 && /* uname() */ - mib[0] == CTL_HW && mib[1] == HW_MACHINE) - return (0); - if (miblen == 2 && /* getpagesize() */ - mib[0] == CTL_HW && mib[1] == HW_PAGESIZE) - return (0); - if (miblen == 2 && /* setproctitle() */ - mib[0] == CTL_VM && mib[1] == VM_PSSTRINGS) - return (0); - - printf("%s(%d): sysctl %d: %d %d %d %d %d %d\n", - p->p_comm, p->p_pid, miblen, mib[0], mib[1], - mib[2], mib[3], mib[4], mib[5]); - return (EFAULT); -} - -int -pledge_adjtime_check(struct proc *p, const void *v) -{ - const struct timeval *delta = v; - - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - if (delta) - return (EFAULT); - return (0); -} - -int -pledge_connect_check(struct proc *p) -{ - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE)) - return (0); /* A port check happens inside sys_connect() */ - - if ((p->p_p->ps_pledge & (PLEDGE_INET | PLEDGE_UNIX))) - return (0); - return (EPERM); -} - -int -pledge_recvfrom_check(struct proc *p, void *v) -{ - struct sockaddr *from = v; - - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && from == NULL) - return (0); - if (p->p_p->ps_pledge & PLEDGE_INET) - return (0); - if (p->p_p->ps_pledge & PLEDGE_UNIX) - return (0); - if (from == NULL) - return (0); /* behaves just like write */ - return (EPERM); -} - -int -pledge_sendto_check(struct proc *p, const void *v) -{ - const struct sockaddr *to = v; - - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && to == NULL) - return (0); - - if ((p->p_p->ps_pledge & PLEDGE_INET)) - return (0); - if ((p->p_p->ps_pledge & PLEDGE_UNIX)) - return (0); - if (to == NULL) - return (0); /* behaves just like write */ - return (EPERM); -} - -int -pledge_socket_check(struct proc *p, int domain) -{ - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - if ((p->p_p->ps_pledge & (PLEDGE_INET | PLEDGE_UNIX))) - return (0); - if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && - (domain == AF_INET || domain == AF_INET6)) - return (0); - return (EPERM); -} - -int -pledge_bind_check(struct proc *p, const void *v) -{ - - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - if ((p->p_p->ps_pledge & PLEDGE_INET)) - return (0); - return (EPERM); -} - -int -pledge_ioctl_check(struct proc *p, long com, void *v) -{ - struct file *fp = v; - struct vnode *vp = NULL; - - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - /* - * The ioctl's which are always allowed. - */ - switch (com) { - case FIONREAD: - case FIONBIO: - return (0); - } - - if (fp == NULL) - return (EBADF); - vp = (struct vnode *)fp->f_data; - - /* - * Further sets of ioctl become available, but are checked a - * bit more carefully against the vnode. - */ - if ((p->p_p->ps_pledge & PLEDGE_IOCTL)) { - switch (com) { - case FIOCLEX: - case FIONCLEX: - case FIOASYNC: - case FIOSETOWN: - case FIOGETOWN: - return (0); - case TIOCGETA: - case TIOCGPGRP: - case TIOCGWINSZ: /* various programs */ - if (fp->f_type == DTYPE_VNODE && (vp->v_flag & VISTTY)) - return (0); - break; - case BIOCGSTATS: /* bpf: tcpdump privsep on ^C */ - if (fp->f_type == DTYPE_VNODE && - fp->f_ops->fo_ioctl == vn_ioctl) - return (0); - break; - case MTIOCGET: - case MTIOCTOP: - /* for pax(1) and such, checking tapes... */ - if (fp->f_type == DTYPE_VNODE && - (vp->v_type == VCHR || vp->v_type == VBLK)) - return (0); - break; - case SIOCGIFGROUP: - if ((p->p_p->ps_pledge & PLEDGE_INET) && - fp->f_type == DTYPE_SOCKET) - return (0); - break; - } - } - - if ((p->p_p->ps_pledge & PLEDGE_ROUTE)) { - switch (com) { - case SIOCGIFADDR: - case SIOCGIFFLAGS: - case SIOCGIFRDOMAIN: - if (fp->f_type == DTYPE_SOCKET) - return (0); - break; - } - } - - if ((p->p_p->ps_pledge & PLEDGE_TTY)) { - switch (com) { - case TIOCSPGRP: - if ((p->p_p->ps_pledge & PLEDGE_PROC) == 0) - break; - /* FALTHROUGH */ - case TIOCGETA: - case TIOCGPGRP: - case TIOCGWINSZ: /* various programs */ -#if notyet - case TIOCSTI: /* ksh? csh? */ -#endif - case TIOCSBRK: /* cu */ - case TIOCCDTR: /* cu */ - case TIOCSETA: /* cu, ... */ - case TIOCSETAW: /* cu, ... */ - case TIOCSETAF: /* tcsetattr TCSAFLUSH, script */ - if (fp->f_type == DTYPE_VNODE && (vp->v_flag & VISTTY)) - return (0); - break; - } - } - - return pledge_fail(p, EPERM, PLEDGE_IOCTL); -} - -int -pledge_setsockopt_check(struct proc *p, int level, int optname) -{ - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - /* common case for PLEDGE_UNIX and PLEDGE_INET */ - switch (level) { - case SOL_SOCKET: - switch (optname) { - case SO_RTABLE: - return (EPERM); - } - return (0); - } - - if ((p->p_p->ps_pledge & PLEDGE_INET) == 0) - return (EPERM); - - switch (level) { - case IPPROTO_TCP: - switch (optname) { - case TCP_NODELAY: - case TCP_MD5SIG: - case TCP_SACK_ENABLE: - case TCP_MAXSEG: - case TCP_NOPUSH: - return (0); - } - break; - case IPPROTO_IP: - switch (optname) { - case IP_TOS: - case IP_TTL: - case IP_MINTTL: - case IP_PORTRANGE: - case IP_RECVDSTADDR: - return (0); - case IP_MULTICAST_IF: - case IP_ADD_MEMBERSHIP: - case IP_DROP_MEMBERSHIP: - if ((p->p_p->ps_pledge & PLEDGE_MCAST) == 0) - return (0); - break; - } - break; - case IPPROTO_ICMP: - break; - case IPPROTO_IPV6: - switch (optname) { - case IPV6_UNICAST_HOPS: - case IPV6_RECVHOPLIMIT: - case IPV6_PORTRANGE: - case IPV6_RECVPKTINFO: -#ifdef notyet - case IPV6_V6ONLY: -#endif - return (0); - case IPV6_MULTICAST_IF: - case IPV6_JOIN_GROUP: - case IPV6_LEAVE_GROUP: - if ((p->p_p->ps_pledge & PLEDGE_MCAST) == 0) - return (0); - break; - } - break; - case IPPROTO_ICMPV6: - break; - } - return (EPERM); -} - -int -pledge_dns_check(struct proc *p, in_port_t port) -{ - if ((p->p_p->ps_flags & PS_PLEDGE) == 0) - return (0); - - if ((p->p_p->ps_pledge & PLEDGE_INET)) - return (0); - if ((p->p_p->ps_pledge & PLEDGE_DNS_ACTIVE) && port == htons(53)) - return (0); /* Allow a DNS connect outbound */ - return (EPERM); -} - -void -pledge_dropwpaths(struct process *pr) -{ - if (pr->ps_pledgepaths && --pr->ps_pledgepaths->wl_ref == 0) { - struct whitepaths *wl = pr->ps_pledgepaths; - int i; - - for (i = 0; i < wl->wl_count; i++) - free(wl->wl_paths[i].name, M_TEMP, wl->wl_paths[i].len); - free(wl, M_TEMP, wl->wl_size); - } - pr->ps_pledgepaths = NULL; -} - -int -canonpath(const char *input, char *buf, size_t bufsize) -{ - char *p, *q, *s, *end; - - /* can't canon relative paths, don't bother */ - if (input[0] != '/') { - if (strlcpy(buf, input, bufsize) >= bufsize) - return (ENAMETOOLONG); - return (0); - } - - /* easiest to work with strings always ending in '/' */ - if (snprintf(buf, bufsize, "%s/", input) >= bufsize) - return (ENAMETOOLONG); - - /* after this we will only be shortening the string. */ - p = buf; - q = p; - while (*p) { - if (p[0] == '/' && p[1] == '/') { - p += 1; - } else if (p[0] == '/' && p[1] == '.' && - p[2] == '/') { - p += 2; - } else { - *q++ = *p++; - } - } - *q = 0; - - end = buf + strlen(buf); - s = buf; - p = s; - while (1) { - /* find "/../" (where's strstr when you need it?) */ - while (p < end) { - if (p[0] == '/' && strncmp(p + 1, "../", 3) == 0) - break; - p++; - } - if (p == end) - break; - if (p == s) { - memmove(s, p + 3, end - p - 3 + 1); - end -= 3; - } else { - /* s starts with '/', so we know there's one - * somewhere before p. */ - q = p - 1; - while (*q != '/') - q--; - memmove(q, p + 3, end - p - 3 + 1); - end -= p + 3 - q; - p = q; - } - } - if (end > s + 1) - *(end - 1) = 0; /* remove trailing '/' */ - - return 0; -} diff --git a/sys/kern/sys_generic.c b/sys/kern/sys_generic.c index b57af106420..2c5ea05e8e0 100644 --- a/sys/kern/sys_generic.c +++ b/sys/kern/sys_generic.c @@ -1,4 +1,4 @@ -/* $OpenBSD: sys_generic.c,v 1.103 2015/09/11 15:29:47 deraadt Exp $ */ +/* $OpenBSD: sys_generic.c,v 1.104 2015/10/09 01:17:21 deraadt Exp $ */ /* $NetBSD: sys_generic.c,v 1.24 1996/03/29 00:25:32 cgd Exp $ */ /* @@ -56,7 +56,7 @@ #include #endif #include -#include +#include #include #include @@ -404,8 +404,8 @@ sys_ioctl(struct proc *p, void *v, register_t *retval) fdp = p->p_fd; fp = fd_getfile_mode(fdp, SCARG(uap, fd), FREAD|FWRITE); - if (tame_ioctl_check(p, com, fp)) - return (tame_fail(p, EPERM, TAME_IOCTL)); + if (pledge_ioctl_check(p, com, fp)) + return (pledge_fail(p, EPERM, PLEDGE_IOCTL)); if (fp == NULL) return (EBADF); diff --git a/sys/sys/syscall_mi.h b/sys/sys/syscall_mi.h index 7ae8572c177..0219db0663e 100644 --- a/sys/sys/syscall_mi.h +++ b/sys/sys/syscall_mi.h @@ -1,4 +1,4 @@ -/* $OpenBSD: syscall_mi.h,v 1.10 2015/09/12 16:22:46 deraadt Exp $ */ +/* $OpenBSD: syscall_mi.h,v 1.11 2015/10/09 01:17:18 deraadt Exp $ */ /* * Copyright (c) 1982, 1986, 1989, 1993 @@ -31,7 +31,7 @@ * @(#)kern_xxx.c 8.2 (Berkeley) 11/14/93 */ -#include +#include #ifdef KTRACE #include @@ -51,7 +51,7 @@ mi_syscall(struct proc *p, register_t code, const struct sysent *callp, register_t *argp, register_t retval[2]) { int lock = !(callp->sy_flags & SY_NOLOCK); - int error, tamed, tval; + int error, pledged, tval; /* refresh the thread's cache of the process's creds */ refreshcreds(p); @@ -71,16 +71,16 @@ mi_syscall(struct proc *p, register_t code, const struct sysent *callp, if (lock) KERNEL_LOCK(); - tamed = (p->p_p->ps_flags & PS_TAMED); - if (tamed && !(tval = tame_check(p, code))) { + pledged = (p->p_p->ps_flags & PS_PLEDGE); + if (pledged && !(tval = pledge_check(p, code))) { if (!lock) KERNEL_LOCK(); - error = tame_fail(p, EPERM, tval); + error = pledge_fail(p, EPERM, tval); KERNEL_UNLOCK(); return (error); } #if NSYSTRACE > 0 - if (!tamed && ISSET(p->p_flag, P_SYSTRACE)) { + if (!pledged && ISSET(p->p_flag, P_SYSTRACE)) { if (!lock) KERNEL_LOCK(); error = systrace_redirect(code, p, argp, retval); @@ -89,8 +89,8 @@ mi_syscall(struct proc *p, register_t code, const struct sysent *callp, } #endif error = (*callp->sy_call)(p, argp, retval); - if (tamed && p->p_tameafter) - tame_aftersyscall(p, code, error); + if (pledged && p->p_pledgeafter) + pledge_aftersyscall(p, code, error); if (lock) KERNEL_UNLOCK();