/* $OpenBSD: kern_tame.c,v 1.18 2015/07/29 17:55: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 tame_syscalls[SYS_MAXSYSCALL] = { [SYS_exit] = 0xffffffff, [SYS_kbind] = 0xffffffff, [SYS_getuid] = _TM_SELF, [SYS_geteuid] = _TM_SELF, [SYS_getresuid] = _TM_SELF, [SYS_getgid] = _TM_SELF, [SYS_getegid] = _TM_SELF, [SYS_getresgid] = _TM_SELF, [SYS_getgroups] = _TM_SELF, [SYS_getlogin] = _TM_SELF, [SYS_getpgrp] = _TM_SELF, [SYS_getpgid] = _TM_SELF, [SYS_getppid] = _TM_SELF, [SYS_getsid] = _TM_SELF, [SYS_getthrid] = _TM_SELF, [SYS_getrlimit] = _TM_SELF, [SYS_gettimeofday] = _TM_SELF, [SYS_getdtablecount] = _TM_SELF, [SYS_issetugid] = _TM_SELF, [SYS_clock_getres] = _TM_SELF, [SYS_clock_gettime] = _TM_SELF, [SYS_getpid] = _TM_SELF, [SYS_umask] = _TM_SELF, [SYS___sysctl] = _TM_SELF, /* read-only; narrow subset */ [SYS_adjtime] = _TM_SELF, /* read-only */ [SYS_chdir] = _TM_RPATH, [SYS_fchdir] = _TM_SELF, /* careful of directory fd inside jails */ /* needed by threaded programs */ [SYS_sched_yield] = _TM_SELF, [SYS___thrsleep] = _TM_SELF, [SYS___thrwakeup] = _TM_SELF, [SYS___threxit] = _TM_SELF, [SYS___thrsigdivert] = _TM_SELF, [SYS_sendsyslog] = _TM_SELF, [SYS_nanosleep] = _TM_SELF, [SYS_sigprocmask] = _TM_SELF, [SYS_sigaction] = _TM_SELF, [SYS_sigreturn] = _TM_SELF, [SYS_getitimer] = _TM_SELF, [SYS_setitimer] = _TM_SELF, [SYS_tame] = _TM_SELF, [SYS_wait4] = _TM_SELF, [SYS_poll] = _TM_RW, [SYS_kevent] = _TM_RW, [SYS_kqueue] = _TM_RW, [SYS_select] = _TM_RW, [SYS_close] = _TM_RW, [SYS_dup] = _TM_RW, [SYS_dup2] = _TM_RW, [SYS_dup3] = _TM_RW, [SYS_closefrom] = _TM_RW, [SYS_shutdown] = _TM_RW, [SYS_read] = _TM_RW, [SYS_readv] = _TM_RW, [SYS_pread] = _TM_RW, [SYS_preadv] = _TM_RW, [SYS_write] = _TM_RW, [SYS_writev] = _TM_RW, [SYS_pwrite] = _TM_RW, [SYS_pwritev] = _TM_RW, [SYS_ftruncate] = _TM_RW, [SYS_lseek] = _TM_RW, [SYS_utimes] = _TM_RW, [SYS_futimes] = _TM_RW, [SYS_utimensat] = _TM_RW, [SYS_futimens] = _TM_RW, [SYS_fcntl] = _TM_RW, [SYS_fsync] = _TM_RW, [SYS_pipe] = _TM_RW, [SYS_pipe2] = _TM_RW, [SYS_socketpair] = _TM_RW, [SYS_getdents] = _TM_RW, [SYS_sendto] = _TM_RW | _TM_DNS_ACTIVE | _TM_YP_ACTIVE, [SYS_sendmsg] = _TM_RW, [SYS_recvmsg] = _TM_RW, [SYS_recvfrom] = _TM_RW | _TM_DNS_ACTIVE | _TM_YP_ACTIVE, [SYS_fork] = _TM_PROC, [SYS_vfork] = _TM_PROC, [SYS_kill] = _TM_PROC, [SYS_setgroups] = _TM_PROC, [SYS_setresgid] = _TM_PROC, [SYS_setresuid] = _TM_PROC, [SYS_ioctl] = _TM_IOCTL, /* very limited subset */ [SYS_getentropy] = _TM_MALLOC, [SYS_madvise] = _TM_MALLOC, [SYS_minherit] = _TM_MALLOC, [SYS_mmap] = _TM_MALLOC, [SYS_mprotect] = _TM_MALLOC, [SYS_mquery] = _TM_MALLOC, [SYS_munmap] = _TM_MALLOC, [SYS___getcwd] = _TM_RPATH | _TM_WPATH, [SYS_open] = _TM_SELF, [SYS_openat] = _TM_RPATH | _TM_WPATH, [SYS_stat] = _TM_SELF, [SYS_fstatat] = _TM_RPATH | _TM_WPATH, [SYS_access] = _TM_SELF, [SYS_faccessat] = _TM_RPATH | _TM_WPATH, [SYS_readlink] = _TM_SELF, [SYS_readlinkat] = _TM_RPATH | _TM_WPATH, [SYS_lstat] = _TM_RPATH | _TM_WPATH | _TM_TMPPATH | _TM_DNSPATH, [SYS_chmod] = _TM_RPATH | _TM_WPATH | _TM_TMPPATH, [SYS_fchmod] = _TM_RPATH | _TM_WPATH, [SYS_fchmodat] = _TM_RPATH | _TM_WPATH, [SYS_chflags] = _TM_RPATH | _TM_WPATH | _TM_TMPPATH, [SYS_chflagsat] = _TM_RPATH | _TM_WPATH, [SYS_chown] = _TM_RPATH | _TM_WPATH | _TM_TMPPATH, [SYS_fchown] = _TM_RPATH | _TM_WPATH, [SYS_fchownat] = _TM_RPATH | _TM_WPATH, [SYS_rename] = _TM_CPATH, [SYS_rmdir] = _TM_CPATH, [SYS_renameat] = _TM_CPATH, [SYS_link] = _TM_CPATH, [SYS_linkat] = _TM_CPATH, [SYS_symlink] = _TM_CPATH, [SYS_unlink] = _TM_CPATH | _TM_TMPPATH, [SYS_unlinkat] = _TM_CPATH, [SYS_mkdir] = _TM_CPATH, [SYS_mkdirat] = _TM_CPATH, [SYS_fstat] = _TM_RW | _TM_RPATH | _TM_WPATH | _TM_TMPPATH, /* rare */ [SYS_socket] = _TM_INET | _TM_UNIX | _TM_DNS_ACTIVE | _TM_YP_ACTIVE, [SYS_listen] = _TM_INET | _TM_UNIX, [SYS_bind] = _TM_INET | _TM_UNIX, [SYS_connect] = _TM_INET | _TM_UNIX | _TM_DNS_ACTIVE | _TM_YP_ACTIVE, [SYS_accept4] = _TM_INET | _TM_UNIX, [SYS_accept] = _TM_INET | _TM_UNIX, [SYS_getpeername] = _TM_INET | _TM_UNIX, [SYS_getsockname] = _TM_INET | _TM_UNIX, [SYS_setsockopt] = _TM_INET | _TM_UNIX, /* small subset */ [SYS_getsockopt] = _TM_INET | _TM_UNIX, [SYS_flock] = _TM_GETPW, }; int sys_tame(struct proc *p, void *v, register_t *retval) { #if 1 return (ENOSYS); #else struct sys_tame_args /* { syscallarg(int) flags; } */ *uap = v; int flags = SCARG(uap, flags); flags &= _TM_USERSET; if ((p->p_p->ps_flags & PS_TAMED) == 0) { p->p_p->ps_flags |= PS_TAMED; p->p_p->ps_tame = flags; return (0); } /* May not set new bits */ if (((flags | p->p_p->ps_tame) & _TM_USERSET) != (p->p_p->ps_tame & _TM_USERSET)) return (EPERM); /* More tame bits being cleared. Force re-learning of _ACTIVE things */ p->p_p->ps_tame &= flags; p->p_p->ps_tame &= _TM_USERSET; return (0); #endif } int tame_check(struct proc *p, int code) { p->p_tamenote = p->p_tameafter = 0; /* XX optimise? */ p->p_tame_syscall = code; if (code < 0 || code > SYS_MAXSYSCALL - 1) return (0); if (p->p_p->ps_tame == 0) return (code == SYS_exit || code == SYS_kbind); return (p->p_p->ps_tame & tame_syscalls[code]); } int tame_fail(struct proc *p, int error, int code) { printf("tame: pid %d %s syscall %d\n", p->p_pid, p->p_comm, p->p_tame_syscall); if (p->p_p->ps_tame & _TM_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_tame = 0; /* Disable all TAME_ flags */ return (error); } /* * Need to make it more obvious that one cannot get through here * without the right flags set */ int tame_namei(struct proc *p, char *origpath) { char path[PATH_MAX]; if (p->p_tamenote == TMN_COREDUMP) return (0); /* Allow a coredump */ if (canonpath(origpath, path, sizeof(path)) != 0) return (tame_fail(p, EPERM, TAME_RPATH)); /* Detect what looks like a mkstemp(3) family operation */ if ((p->p_p->ps_tame & _TM_TMPPATH) && (p->p_tame_syscall == SYS_open) && (p->p_tamenote & (TMN_CREAT | TMN_IMODIFY)) == TMN_CREAT && 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_tame & _TM_TMPPATH) && (p->p_tame_syscall == SYS_unlink) && strncmp(path, "/tmp/", sizeof("/tmp") - 1) == 0) { return (0); } /* open, mkdir, or other path creation operation */ if ((p->p_tamenote & (TMN_CREAT | TMN_IMODIFY)) == TMN_CREAT && ((p->p_p->ps_tame & _TM_CPATH) == 0)) return (tame_fail(p, EPERM, TAME_CPATH)); /* inode change operation, issued against a path */ if ((p->p_tamenote & (TMN_CREAT | TMN_IMODIFY)) == TMN_IMODIFY && ((p->p_p->ps_tame & _TM_CPATH) == 0)) { // XXX should _TM_CPATH be a seperate check? return (tame_fail(p, EPERM, TAME_CPATH)); } if ((p->p_tamenote & TMN_WRITE) && (p->p_p->ps_tame & _TM_WPATH) == 0) return (tame_fail(p, EPERM, TAME_WPATH)); if (p->p_p->ps_tame & _TM_RPATH) return (0); if (p->p_p->ps_tame & _TM_WPATH) return (0); /* All remaining cases are RPATH */ switch (p->p_tame_syscall) { case SYS_access: /* tzset() needs this. */ if (strcmp(path, "/etc/localtime") == 0) return (0); break; case SYS_open: /* getpw* and friends need a few files */ if (p->p_p->ps_tame & _TM_GETPW) { if (strcmp(path, "/etc/spwd.db") == 0) return (0); 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_p->ps_tame & _TM_DNSPATH) { if (strcmp(path, "/etc/resolv.conf") == 0) { p->p_tamenote |= TMN_DNSRESOLV; p->p_tameafter = 1; return (0); } if (strcmp(path, "/etc/hosts") == 0) return (0); if (strcmp(path, "/etc/services") == 0) return (0); } if (p->p_p->ps_tame & _TM_GETPW) { if (strcmp(path, "/var/run/ypbind.lock") == 0) { p->p_tamenote |= TMN_YPLOCK; p->p_tameafter = 1; return (0); } if (strncmp(path, "/var/yp/binding/", sizeof("/var/yp/binding/") - 1) == 0) return (0); } /* tzset() needs these. */ if (strncmp(path, "/usr/share/zoneinfo/", sizeof("/usr/share/zoneinfo/") - 1) == 0) return (0); if (strcmp(path, "/etc/localtime") == 0) return (0); /* /usr/share/nls/../libc.cat returns EPERM, for strerror(3). */ if (strncmp(path, "/usr/share/nls/", sizeof("/usr/share/nls/") - 1) == 0 && strcmp(path + strlen(path) - 9, "/libc.cat") == 0) return (EPERM); break; case SYS_readlink: /* Allow /etc/malloc.conf for malloc(3). */ if (strcmp(path, "/etc/malloc.conf") == 0) return (0); break; case SYS_stat: /* DNS needs /etc/resolv.conf. */ if (p->p_p->ps_tame & _TM_DNSPATH) { if (strcmp(path, "/etc/resolv.conf") == 0) { p->p_tamenote |= TMN_DNSRESOLV; p->p_tameafter = 1; return (0); } } break; } return (tame_fail(p, EPERM, TAME_RPATH)); } void tame_aftersyscall(struct proc *p, int code, int error) { if ((p->p_tamenote & TMN_YPLOCK) && error == 0) atomic_setbits_int(&p->p_p->ps_tame, _TM_YP_ACTIVE | TAME_INET); if ((p->p_tamenote & TMN_DNSRESOLV) && error == 0) atomic_setbits_int(&p->p_p->ps_tame, _TM_DNS_ACTIVE); } /* * By default, only the advisory cmsg's can be received from the kernel, * such as TIMESTAMP ntpd. * * If TAME_CMSG is set SCM_RIGHTS is also allowed through 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 tame_cmsg_recv(struct proc *p, void *v, int controllen) { struct mbuf *control = v; struct msghdr tmp; struct cmsghdr *cmsg; int *fdp, fd; struct file *fp; int nfds, i; if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); /* Scan the cmsg */ memset(&tmp, 0, sizeof(tmp)); tmp.msg_control = mtod(control, struct cmsghdr *); tmp.msg_controllen = controllen; 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_tame & _TM_CMSG) == 0) return tame_fail(p, EPERM, TAME_CMSG); /* 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 tame_fail(p, EBADF, TAME_CMSG); /* Only allow passing of sockets, pipes, and pure files */ printf("f_type %d\n", fp->f_type); switch (fp->f_type) { case DTYPE_SOCKET: case DTYPE_PIPE: continue; case DTYPE_VNODE: vp = (struct vnode *)fp->f_data; printf("v_type %d\n", vp->v_type); if (vp->v_type == VREG) continue; break; default: break; } printf("bad fd type\n"); return tame_fail(p, EPERM, TAME_CMSG); } return (0); } /* * When tamed, default prevents sending of a cmsg. * If CMSG flag is set, */ int tame_cmsg_send(struct proc *p, void *v, int controllen) { struct mbuf *control = v; struct msghdr tmp; struct cmsghdr *cmsg; int *fdp, fd; struct file *fp; int nfds, i; if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if ((p->p_p->ps_tame & _TM_CMSG) == 0) return tame_fail(p, EPERM, TAME_CMSG); /* Scan the cmsg */ memset(&tmp, 0, sizeof(tmp)); tmp.msg_control = mtod(control, struct cmsghdr *); tmp.msg_controllen = controllen; cmsg = CMSG_FIRSTHDR(&tmp); while (cmsg != NULL) { if (cmsg->cmsg_level == SOL_SOCKET && cmsg->cmsg_type == SCM_RIGHTS) break; cmsg = CMSG_NXTHDR(&tmp, cmsg); } /* Contains no SCM_RIGHTS, so OK */ if (cmsg == NULL) 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 tame_fail(p, EBADF, TAME_CMSG); /* Only allow passing of sockets, pipes, and pure files */ printf("f_type %d\n", fp->f_type); switch (fp->f_type) { case DTYPE_SOCKET: case DTYPE_PIPE: continue; case DTYPE_VNODE: vp = (struct vnode *)fp->f_data; printf("v_type %d\n", vp->v_type); if (vp->v_type == VREG) continue; break; default: break; } /* Not allowed to send a bad fd type */ return tame_fail(p, EPERM, TAME_CMSG); } return (0); } int tame_sysctl_check(struct proc *p, int namelen, int *name, void *new) { if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if (new) return (EFAULT); /* getifaddrs() */ if ((p->p_p->ps_tame & _TM_INET) && namelen == 6 && name[0] == CTL_NET && name[1] == PF_ROUTE && name[2] == 0 && name[3] == 0 && name[4] == NET_RT_IFLIST && name[5] == 0) return (0); /* used by arp(8). Exposes MAC addresses known on local nets */ /* XXX Put into a special catagory. */ if ((p->p_p->ps_tame & _TM_INET) && namelen == 7 && name[0] == CTL_NET && name[1] == PF_ROUTE && name[2] == 0 && name[3] == AF_INET && name[4] == NET_RT_FLAGS && name[5] == RTF_LLINFO) return (0); /* used by ntpd(8) to read sensors. */ /* XXX Put into a special catagory. */ if (namelen >= 3 && name[0] == CTL_HW && name[1] == HW_SENSORS) return (0); /* getdomainname(), gethostname(), getpagesize(), uname() */ if (namelen == 2 && name[0] == CTL_KERN && name[1] == KERN_DOMAINNAME) return (0); if (namelen == 2 && name[0] == CTL_KERN && name[1] == KERN_HOSTNAME) return (0); if (namelen == 2 && name[0] == CTL_KERN && name[1] == KERN_OSTYPE) return (0); if (namelen == 2 && name[0] == CTL_KERN && name[1] == KERN_OSRELEASE) return (0); if (namelen == 2 && name[0] == CTL_KERN && name[1] == KERN_OSVERSION) return (0); if (namelen == 2 && name[0] == CTL_KERN && name[1] == KERN_VERSION) return (0); if (namelen == 2 && name[0] == CTL_HW && name[1] == HW_MACHINE) return (0); if (namelen == 2 && name[0] == CTL_HW && name[1] == HW_PAGESIZE) return (0); printf("tame: pid %d %s sysctl %d: %d %d %d %d %d %d\n", p->p_pid, p->p_comm, namelen, name[0], name[1], name[2], name[3], name[4], name[5]); return (EFAULT); } int tame_adjtime_check(struct proc *p, const void *v) { const struct timeval *delta = v; if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if (delta) return (EFAULT); return (0); } int tame_connect_check(struct proc *p) { if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if ((p->p_p->ps_tame & _TM_DNS_ACTIVE)) return (0); /* A port check happens inside sys_connect() */ if ((p->p_p->ps_tame & (_TM_INET | _TM_UNIX))) return (0); return (EPERM); } int tame_recvfrom_check(struct proc *p, void *v) { struct sockaddr *from = v; if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if ((p->p_p->ps_tame & _TM_DNS_ACTIVE) && from == NULL) return (0); if (p->p_p->ps_tame & _TM_INET) return (0); if (p->p_p->ps_tame & _TM_UNIX) return (0); if (from == NULL) return (0); /* behaves just like write */ return (EPERM); } int tame_sendto_check(struct proc *p, const void *v) { const struct sockaddr *to = v; if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if ((p->p_p->ps_tame & _TM_DNS_ACTIVE) && to == NULL) return (0); if ((p->p_p->ps_tame & _TM_INET)) return (0); if ((p->p_p->ps_tame & _TM_UNIX)) return (0); if (to == NULL) return (0); /* behaves just like write */ return (EPERM); } int tame_socket_check(struct proc *p, int domain) { if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if ((p->p_p->ps_tame & (_TM_INET | _TM_UNIX))) return (0); if ((p->p_p->ps_tame & _TM_DNS_ACTIVE) && domain == AF_INET) return (0); return (EPERM); } int tame_bind_check(struct proc *p, const void *v) { if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if ((p->p_p->ps_tame & _TM_INET)) return (0); return (EPERM); } int tame_ioctl_check(struct proc *p, long com, void *v) { struct file *fp = v; struct vnode *vp = NULL; if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if (fp == NULL) return (EBADF); vp = (struct vnode *)fp->f_data; switch (com) { /* * This is a set of "get" info ioctls at the top layer. Hopefully * a safe list, since they are used a lot. */ case FIOCLEX: case FIONCLEX: case FIONREAD: case FIONBIO: case FIOGETOWN: return (0); case FIOASYNC: case FIOSETOWN: return (EPERM); /* tty subsystem */ case TIOCGWINSZ: /* various programs */ case TIOCSTI: /* ksh? csh? */ if (fp->f_type == DTYPE_VNODE && (vp->v_flag & VISTTY)) return (0); break; default: break; } if ((p->p_p->ps_tame & _TM_IOCTL) == 0) return (EPERM); /* * Further sets of ioctl become available, but are checked a * bit more carefully against the vnode. */ switch (com) { case BIOCGSTATS: /* bpf: tcpdump privsep on ^C */ if (fp->f_type == DTYPE_VNODE && fp->f_ops->fo_ioctl == vn_ioctl) return (0); break; case TIOCSETAF: /* tcsetattr TCSAFLUSH, script */ if (fp->f_type == DTYPE_VNODE && (vp->v_flag & VISTTY)) 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_tame & _TM_INET) && fp->f_type == DTYPE_SOCKET) return (0); break; default: printf("ioctl %lx\n", com); break; } return (EPERM); } int tame_setsockopt_check(struct proc *p, int level, int optname) { if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); switch (level) { case SOL_SOCKET: switch (optname) { case SO_RTABLE: return (EPERM); } return (0); case IPPROTO_TCP: switch (optname) { case TCP_NODELAY: case TCP_MD5SIG: case TCP_SACK_ENABLE: case TCP_MAXSEG: case TCP_NOPUSH: return (0); } case IPPROTO_IP: switch (optname) { case IP_TOS: case IP_TTL: case IP_MINTTL: case IP_PORTRANGE: case IP_RECVDSTADDR: return (0); } case IPPROTO_ICMP: break; case IPPROTO_IPV6: case IPPROTO_ICMPV6: break; } return (EPERM); } int tame_dns_check(struct proc *p, in_port_t port) { if ((p->p_p->ps_flags & PS_TAMED) == 0) return (0); if ((p->p_p->ps_tame & _TM_INET)) return (0); if ((p->p_p->ps_tame & _TM_DNS_ACTIVE) && port == htons(53)) return (0); /* Allow a DNS connect outbound */ return (EPERM); } 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 (EINVAL); return (0); } /* easiest to work with strings always ending in '/' */ if (snprintf(buf, bufsize, "%s/", input) >= bufsize) return (EINVAL); /* 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; }