summaryrefslogtreecommitdiffstats
path: root/sys/kern/sys_capability.c
diff options
context:
space:
mode:
Diffstat (limited to 'sys/kern/sys_capability.c')
-rw-r--r--sys/kern/sys_capability.c613
1 files changed, 613 insertions, 0 deletions
diff --git a/sys/kern/sys_capability.c b/sys/kern/sys_capability.c
new file mode 100644
index 0000000..7a82017
--- /dev/null
+++ b/sys/kern/sys_capability.c
@@ -0,0 +1,613 @@
+/*-
+ * Copyright (c) 2008-2011 Robert N. M. Watson
+ * Copyright (c) 2010-2011 Jonathan Anderson
+ * Copyright (c) 2012 FreeBSD Foundation
+ * All rights reserved.
+ *
+ * This software was developed at the University of Cambridge Computer
+ * Laboratory with support from a grant from Google, Inc.
+ *
+ * Portions of this software were developed by Pawel Jakub Dawidek under
+ * sponsorship from the FreeBSD Foundation.
+ *
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions
+ * are met:
+ * 1. Redistributions of source code must retain the above copyright
+ * notice, this list of conditions and the following disclaimer.
+ * 2. Redistributions in binary form must reproduce the above copyright
+ * notice, this list of conditions and the following disclaimer in the
+ * documentation and/or other materials provided with the distribution.
+ *
+ * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND
+ * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE
+ * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
+ * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
+ * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
+ * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
+ * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
+ * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
+ * SUCH DAMAGE.
+ */
+
+/*
+ * FreeBSD kernel capability facility.
+ *
+ * Two kernel features are implemented here: capability mode, a sandboxed mode
+ * of execution for processes, and capabilities, a refinement on file
+ * descriptors that allows fine-grained control over operations on the file
+ * descriptor. Collectively, these allow processes to run in the style of a
+ * historic "capability system" in which they can use only resources
+ * explicitly delegated to them. This model is enforced by restricting access
+ * to global namespaces in capability mode.
+ *
+ * Capabilities wrap other file descriptor types, binding them to a constant
+ * rights mask set when the capability is created. New capabilities may be
+ * derived from existing capabilities, but only if they have the same or a
+ * strict subset of the rights on the original capability.
+ *
+ * System calls permitted in capability mode are defined in capabilities.conf;
+ * calls must be carefully audited for safety to ensure that they don't allow
+ * escape from a sandbox. Some calls permit only a subset of operations in
+ * capability mode -- for example, shm_open(2) is limited to creating
+ * anonymous, rather than named, POSIX shared memory objects.
+ */
+
+#include <sys/cdefs.h>
+__FBSDID("$FreeBSD$");
+
+#include "opt_capsicum.h"
+#include "opt_ktrace.h"
+
+#include <sys/param.h>
+#include <sys/capability.h>
+#include <sys/file.h>
+#include <sys/filedesc.h>
+#include <sys/kernel.h>
+#include <sys/limits.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/proc.h>
+#include <sys/syscallsubr.h>
+#include <sys/sysproto.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/ucred.h>
+#include <sys/uio.h>
+#include <sys/ktrace.h>
+
+#include <security/audit/audit.h>
+
+#include <vm/uma.h>
+#include <vm/vm.h>
+
+#ifdef CAPABILITY_MODE
+
+FEATURE(security_capability_mode, "Capsicum Capability Mode");
+
+/*
+ * System call to enter capability mode for the process.
+ */
+int
+sys_cap_enter(struct thread *td, struct cap_enter_args *uap)
+{
+ struct ucred *newcred, *oldcred;
+ struct proc *p;
+
+ if (IN_CAPABILITY_MODE(td))
+ return (0);
+
+ newcred = crget();
+ p = td->td_proc;
+ PROC_LOCK(p);
+ oldcred = p->p_ucred;
+ crcopy(newcred, oldcred);
+ newcred->cr_flags |= CRED_FLAG_CAPMODE;
+ p->p_ucred = newcred;
+ PROC_UNLOCK(p);
+ crfree(oldcred);
+ return (0);
+}
+
+/*
+ * System call to query whether the process is in capability mode.
+ */
+int
+sys_cap_getmode(struct thread *td, struct cap_getmode_args *uap)
+{
+ u_int i;
+
+ i = IN_CAPABILITY_MODE(td) ? 1 : 0;
+ return (copyout(&i, uap->modep, sizeof(i)));
+}
+
+#else /* !CAPABILITY_MODE */
+
+int
+sys_cap_enter(struct thread *td, struct cap_enter_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+int
+sys_cap_getmode(struct thread *td, struct cap_getmode_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+#endif /* CAPABILITY_MODE */
+
+#ifdef CAPABILITIES
+
+FEATURE(security_capabilities, "Capsicum Capabilities");
+
+MALLOC_DECLARE(M_FILECAPS);
+
+static inline int
+_cap_check(const cap_rights_t *havep, const cap_rights_t *needp,
+ enum ktr_cap_fail_type type)
+{
+ int i;
+
+ for (i = 0; i < nitems(havep->cr_rights); i++) {
+ if (!cap_rights_contains(havep, needp)) {
+#ifdef KTRACE
+ if (KTRPOINT(curthread, KTR_CAPFAIL))
+ ktrcapfail(type, needp, havep);
+#endif
+ return (ENOTCAPABLE);
+ }
+ }
+ return (0);
+}
+
+/*
+ * Test whether a capability grants the requested rights.
+ */
+int
+cap_check(const cap_rights_t *havep, const cap_rights_t *needp)
+{
+
+ return (_cap_check(havep, needp, CAPFAIL_NOTCAPABLE));
+}
+
+/*
+ * Convert capability rights into VM access flags.
+ */
+u_char
+cap_rights_to_vmprot(cap_rights_t *havep)
+{
+ u_char maxprot;
+
+ maxprot = VM_PROT_NONE;
+ if (cap_rights_is_set(havep, CAP_MMAP_R))
+ maxprot |= VM_PROT_READ;
+ if (cap_rights_is_set(havep, CAP_MMAP_W))
+ maxprot |= VM_PROT_WRITE;
+ if (cap_rights_is_set(havep, CAP_MMAP_X))
+ maxprot |= VM_PROT_EXECUTE;
+
+ return (maxprot);
+}
+
+/*
+ * Extract rights from a capability for monitoring purposes -- not for use in
+ * any other way, as we want to keep all capability permission evaluation in
+ * this one file.
+ */
+cap_rights_t *
+cap_rights(struct filedesc *fdp, int fd)
+{
+
+ return (&fdp->fd_ofiles[fd].fde_rights);
+}
+
+/*
+ * System call to limit rights of the given capability.
+ */
+int
+sys_cap_rights_limit(struct thread *td, struct cap_rights_limit_args *uap)
+{
+ struct filedesc *fdp;
+ cap_rights_t rights;
+ int error, fd, version;
+
+ cap_rights_init(&rights);
+
+ error = copyin(uap->rightsp, &rights, sizeof(rights.cr_rights[0]));
+ if (error != 0)
+ return (error);
+ version = CAPVER(&rights);
+ if (version != CAP_RIGHTS_VERSION_00)
+ return (EINVAL);
+
+ error = copyin(uap->rightsp, &rights,
+ sizeof(rights.cr_rights[0]) * CAPARSIZE(&rights));
+ if (error != 0)
+ return (error);
+ /* Check for race. */
+ if (CAPVER(&rights) != version)
+ return (EINVAL);
+
+ if (!cap_rights_is_valid(&rights))
+ return (EINVAL);
+
+ if (version != CAP_RIGHTS_VERSION) {
+ rights.cr_rights[0] &= ~(0x3ULL << 62);
+ rights.cr_rights[0] |= ((uint64_t)CAP_RIGHTS_VERSION << 62);
+ }
+#ifdef KTRACE
+ if (KTRPOINT(td, KTR_STRUCT))
+ ktrcaprights(&rights);
+#endif
+
+ fd = uap->fd;
+
+ AUDIT_ARG_FD(fd);
+ AUDIT_ARG_RIGHTS(&rights);
+
+ fdp = td->td_proc->p_fd;
+ FILEDESC_XLOCK(fdp);
+ if (fget_locked(fdp, fd) == NULL) {
+ FILEDESC_XUNLOCK(fdp);
+ return (EBADF);
+ }
+ error = _cap_check(cap_rights(fdp, fd), &rights, CAPFAIL_INCREASE);
+ if (error == 0) {
+ fdp->fd_ofiles[fd].fde_rights = rights;
+ if (!cap_rights_is_set(&rights, CAP_IOCTL)) {
+ free(fdp->fd_ofiles[fd].fde_ioctls, M_FILECAPS);
+ fdp->fd_ofiles[fd].fde_ioctls = NULL;
+ fdp->fd_ofiles[fd].fde_nioctls = 0;
+ }
+ if (!cap_rights_is_set(&rights, CAP_FCNTL))
+ fdp->fd_ofiles[fd].fde_fcntls = 0;
+ }
+ FILEDESC_XUNLOCK(fdp);
+ return (error);
+}
+
+/*
+ * System call to query the rights mask associated with a capability.
+ */
+int
+sys___cap_rights_get(struct thread *td, struct __cap_rights_get_args *uap)
+{
+ struct filedesc *fdp;
+ cap_rights_t rights;
+ int error, fd, i, n;
+
+ if (uap->version != CAP_RIGHTS_VERSION_00)
+ return (EINVAL);
+
+ fd = uap->fd;
+
+ AUDIT_ARG_FD(fd);
+
+ fdp = td->td_proc->p_fd;
+ FILEDESC_SLOCK(fdp);
+ if (fget_locked(fdp, fd) == NULL) {
+ FILEDESC_SUNLOCK(fdp);
+ return (EBADF);
+ }
+ rights = *cap_rights(fdp, fd);
+ FILEDESC_SUNLOCK(fdp);
+ n = uap->version + 2;
+ if (uap->version != CAPVER(&rights)) {
+ /*
+ * For older versions we need to check if the descriptor
+ * doesn't contain rights not understood by the caller.
+ * If it does, we have to return an error.
+ */
+ for (i = n; i < CAPARSIZE(&rights); i++) {
+ if ((rights.cr_rights[i] & ~(0x7FULL << 57)) != 0)
+ return (EINVAL);
+ }
+ }
+ error = copyout(&rights, uap->rightsp, sizeof(rights.cr_rights[0]) * n);
+#ifdef KTRACE
+ if (error == 0 && KTRPOINT(td, KTR_STRUCT))
+ ktrcaprights(&rights);
+#endif
+ return (error);
+}
+
+/*
+ * Test whether a capability grants the given ioctl command.
+ * If descriptor doesn't have CAP_IOCTL, then ioctls list is empty and
+ * ENOTCAPABLE will be returned.
+ */
+int
+cap_ioctl_check(struct filedesc *fdp, int fd, u_long cmd)
+{
+ u_long *cmds;
+ ssize_t ncmds;
+ long i;
+
+ FILEDESC_LOCK_ASSERT(fdp);
+ KASSERT(fd >= 0 && fd < fdp->fd_nfiles,
+ ("%s: invalid fd=%d", __func__, fd));
+
+ ncmds = fdp->fd_ofiles[fd].fde_nioctls;
+ if (ncmds == -1)
+ return (0);
+
+ cmds = fdp->fd_ofiles[fd].fde_ioctls;
+ for (i = 0; i < ncmds; i++) {
+ if (cmds[i] == cmd)
+ return (0);
+ }
+
+ return (ENOTCAPABLE);
+}
+
+/*
+ * Check if the current ioctls list can be replaced by the new one.
+ */
+static int
+cap_ioctl_limit_check(struct filedesc *fdp, int fd, const u_long *cmds,
+ size_t ncmds)
+{
+ u_long *ocmds;
+ ssize_t oncmds;
+ u_long i;
+ long j;
+
+ oncmds = fdp->fd_ofiles[fd].fde_nioctls;
+ if (oncmds == -1)
+ return (0);
+ if (oncmds < (ssize_t)ncmds)
+ return (ENOTCAPABLE);
+
+ ocmds = fdp->fd_ofiles[fd].fde_ioctls;
+ for (i = 0; i < ncmds; i++) {
+ for (j = 0; j < oncmds; j++) {
+ if (cmds[i] == ocmds[j])
+ break;
+ }
+ if (j == oncmds)
+ return (ENOTCAPABLE);
+ }
+
+ return (0);
+}
+
+int
+kern_cap_ioctls_limit(struct thread *td, int fd, u_long *cmds, size_t ncmds)
+{
+ struct filedesc *fdp;
+ u_long *ocmds;
+ int error;
+
+ AUDIT_ARG_FD(fd);
+
+ fdp = td->td_proc->p_fd;
+ FILEDESC_XLOCK(fdp);
+
+ if (fget_locked(fdp, fd) == NULL) {
+ error = EBADF;
+ goto out;
+ }
+
+ error = cap_ioctl_limit_check(fdp, fd, cmds, ncmds);
+ if (error != 0)
+ goto out;
+
+ ocmds = fdp->fd_ofiles[fd].fde_ioctls;
+ fdp->fd_ofiles[fd].fde_ioctls = cmds;
+ fdp->fd_ofiles[fd].fde_nioctls = ncmds;
+
+ cmds = ocmds;
+ error = 0;
+out:
+ FILEDESC_XUNLOCK(fdp);
+ free(cmds, M_FILECAPS);
+ return (error);
+}
+
+int
+sys_cap_ioctls_limit(struct thread *td, struct cap_ioctls_limit_args *uap)
+{
+ u_long *cmds;
+ size_t ncmds;
+ int error;
+
+ ncmds = uap->ncmds;
+
+ if (ncmds > 256) /* XXX: Is 256 sane? */
+ return (EINVAL);
+
+ if (ncmds == 0) {
+ cmds = NULL;
+ } else {
+ cmds = malloc(sizeof(cmds[0]) * ncmds, M_FILECAPS, M_WAITOK);
+ error = copyin(uap->cmds, cmds, sizeof(cmds[0]) * ncmds);
+ if (error != 0) {
+ free(cmds, M_FILECAPS);
+ return (error);
+ }
+ }
+
+ return (kern_cap_ioctls_limit(td, uap->fd, cmds, ncmds));
+}
+
+int
+sys_cap_ioctls_get(struct thread *td, struct cap_ioctls_get_args *uap)
+{
+ struct filedesc *fdp;
+ struct filedescent *fdep;
+ u_long *cmds;
+ size_t maxcmds;
+ int error, fd;
+
+ fd = uap->fd;
+ cmds = uap->cmds;
+ maxcmds = uap->maxcmds;
+
+ AUDIT_ARG_FD(fd);
+
+ fdp = td->td_proc->p_fd;
+ FILEDESC_SLOCK(fdp);
+
+ if (fget_locked(fdp, fd) == NULL) {
+ error = EBADF;
+ goto out;
+ }
+
+ /*
+ * If all ioctls are allowed (fde_nioctls == -1 && fde_ioctls == NULL)
+ * the only sane thing we can do is to not populate the given array and
+ * return CAP_IOCTLS_ALL.
+ */
+
+ fdep = &fdp->fd_ofiles[fd];
+ if (cmds != NULL && fdep->fde_ioctls != NULL) {
+ error = copyout(fdep->fde_ioctls, cmds,
+ sizeof(cmds[0]) * MIN(fdep->fde_nioctls, maxcmds));
+ if (error != 0)
+ goto out;
+ }
+ if (fdep->fde_nioctls == -1)
+ td->td_retval[0] = CAP_IOCTLS_ALL;
+ else
+ td->td_retval[0] = fdep->fde_nioctls;
+
+ error = 0;
+out:
+ FILEDESC_SUNLOCK(fdp);
+ return (error);
+}
+
+/*
+ * Test whether a capability grants the given fcntl command.
+ */
+int
+cap_fcntl_check(struct filedesc *fdp, int fd, int cmd)
+{
+ uint32_t fcntlcap;
+
+ KASSERT(fd >= 0 && fd < fdp->fd_nfiles,
+ ("%s: invalid fd=%d", __func__, fd));
+
+ fcntlcap = (1 << cmd);
+ KASSERT((CAP_FCNTL_ALL & fcntlcap) != 0,
+ ("Unsupported fcntl=%d.", cmd));
+
+ if ((fdp->fd_ofiles[fd].fde_fcntls & fcntlcap) != 0)
+ return (0);
+
+ return (ENOTCAPABLE);
+}
+
+int
+sys_cap_fcntls_limit(struct thread *td, struct cap_fcntls_limit_args *uap)
+{
+ struct filedesc *fdp;
+ uint32_t fcntlrights;
+ int fd;
+
+ fd = uap->fd;
+ fcntlrights = uap->fcntlrights;
+
+ AUDIT_ARG_FD(fd);
+ AUDIT_ARG_FCNTL_RIGHTS(fcntlrights);
+
+ if ((fcntlrights & ~CAP_FCNTL_ALL) != 0)
+ return (EINVAL);
+
+ fdp = td->td_proc->p_fd;
+ FILEDESC_XLOCK(fdp);
+
+ if (fget_locked(fdp, fd) == NULL) {
+ FILEDESC_XUNLOCK(fdp);
+ return (EBADF);
+ }
+
+ if ((fcntlrights & ~fdp->fd_ofiles[fd].fde_fcntls) != 0) {
+ FILEDESC_XUNLOCK(fdp);
+ return (ENOTCAPABLE);
+ }
+
+ fdp->fd_ofiles[fd].fde_fcntls = fcntlrights;
+ FILEDESC_XUNLOCK(fdp);
+
+ return (0);
+}
+
+int
+sys_cap_fcntls_get(struct thread *td, struct cap_fcntls_get_args *uap)
+{
+ struct filedesc *fdp;
+ uint32_t rights;
+ int fd;
+
+ fd = uap->fd;
+
+ AUDIT_ARG_FD(fd);
+
+ fdp = td->td_proc->p_fd;
+ FILEDESC_SLOCK(fdp);
+ if (fget_locked(fdp, fd) == NULL) {
+ FILEDESC_SUNLOCK(fdp);
+ return (EBADF);
+ }
+ rights = fdp->fd_ofiles[fd].fde_fcntls;
+ FILEDESC_SUNLOCK(fdp);
+
+ return (copyout(&rights, uap->fcntlrightsp, sizeof(rights)));
+}
+
+#else /* !CAPABILITIES */
+
+/*
+ * Stub Capability functions for when options CAPABILITIES isn't compiled
+ * into the kernel.
+ */
+
+int
+sys_cap_rights_limit(struct thread *td, struct cap_rights_limit_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+int
+sys___cap_rights_get(struct thread *td, struct __cap_rights_get_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+int
+sys_cap_ioctls_limit(struct thread *td, struct cap_ioctls_limit_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+int
+sys_cap_ioctls_get(struct thread *td, struct cap_ioctls_get_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+int
+sys_cap_fcntls_limit(struct thread *td, struct cap_fcntls_limit_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+int
+sys_cap_fcntls_get(struct thread *td, struct cap_fcntls_get_args *uap)
+{
+
+ return (ENOSYS);
+}
+
+#endif /* CAPABILITIES */
OpenPOWER on IntegriCloud