]> git.saurik.com Git - apple/xnu.git/blobdiff - bsd/kern/kern_guarded.c
xnu-2422.1.72.tar.gz
[apple/xnu.git] / bsd / kern / kern_guarded.c
diff --git a/bsd/kern/kern_guarded.c b/bsd/kern/kern_guarded.c
new file mode 100644 (file)
index 0000000..5c175c7
--- /dev/null
@@ -0,0 +1,683 @@
+/*
+ * Copyright (c) 2012 Apple Inc. All rights reserved.
+ *
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
+ * 
+ * This file contains Original Code and/or Modifications of Original Code
+ * as defined in and that are subject to the Apple Public Source License
+ * Version 2.0 (the 'License'). You may not use this file except in
+ * compliance with the License. The rights granted to you under the License
+ * may not be used to create, or enable the creation or redistribution of,
+ * unlawful or unlicensed copies of an Apple operating system, or to
+ * circumvent, violate, or enable the circumvention or violation of, any
+ * terms of an Apple operating system software license agreement.
+ * 
+ * Please obtain a copy of the License at
+ * http://www.opensource.apple.com/apsl/ and read it before using this file.
+ * 
+ * The Original Code and all software distributed under the License are
+ * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
+ * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
+ * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
+ * Please see the License for the specific language governing rights and
+ * limitations under the License.
+ * 
+ * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
+ */
+
+#include <sys/param.h>
+#include <sys/systm.h>
+#include <sys/filedesc.h>
+#include <sys/kernel.h>
+#include <sys/file_internal.h>
+#include <sys/guarded.h>
+#include <kern/kalloc.h>
+#include <sys/sysproto.h>
+#include <sys/vnode.h>
+#include <vfs/vfs_support.h>
+#include <security/audit/audit.h>
+
+/*
+ * Experimental guarded file descriptor support.
+ */
+
+kern_return_t task_exception_notify(exception_type_t exception,
+        mach_exception_data_type_t code, mach_exception_data_type_t subcode);
+
+/*
+ * Most fd's have an underlying fileproc struct; but some may be
+ * guarded_fileproc structs which implement guarded fds.  The latter
+ * struct (below) embeds the former.
+ *
+ * The two types should be distinguished by the "type" portion of f_flags.
+ * There's also a magic number to help catch misuse and bugs.
+ *
+ * This is a bit unpleasant, but results from the desire to allow
+ * alternate file behaviours for a few file descriptors without
+ * growing the fileproc data structure.
+ */
+
+struct guarded_fileproc {
+       struct fileproc gf_fileproc;
+       u_int           gf_magic;
+       u_int           gf_attrs;
+       thread_t        gf_thread;
+       guardid_t       gf_guard;
+       int             gf_exc_fd;
+       u_int           gf_exc_code;
+};
+
+const size_t sizeof_guarded_fileproc = sizeof (struct guarded_fileproc);
+
+#define FP_TO_GFP(fp)  ((struct guarded_fileproc *)(fp))
+#define        GFP_TO_FP(gfp)  (&(gfp)->gf_fileproc)
+
+#define GUARDED_FILEPROC_MAGIC 0x29083
+
+struct gfp_crarg {
+       guardid_t gca_guard;
+       u_int gca_attrs;
+};
+
+static struct fileproc *
+guarded_fileproc_alloc_init(void *crarg)
+{
+       struct gfp_crarg *aarg = crarg;
+       struct guarded_fileproc *gfp;
+
+       if ((gfp = kalloc(sizeof (*gfp))) == NULL)
+               return (NULL);
+
+       bzero(gfp, sizeof (*gfp));
+       gfp->gf_fileproc.f_flags = FTYPE_GUARDED;
+       gfp->gf_magic = GUARDED_FILEPROC_MAGIC;
+       gfp->gf_guard = aarg->gca_guard;
+       gfp->gf_attrs = aarg->gca_attrs;
+
+       return (GFP_TO_FP(gfp));
+}
+
+void
+guarded_fileproc_free(struct fileproc *fp)
+{
+       struct guarded_fileproc *gfp = FP_TO_GFP(fp);
+
+       if (FILEPROC_TYPE(fp) != FTYPE_GUARDED ||
+           GUARDED_FILEPROC_MAGIC != gfp->gf_magic)
+               panic("%s: corrupt fp %p flags %x", __func__, fp, fp->f_flags);
+
+       kfree(gfp, sizeof (*gfp));
+}
+
+static int
+fp_lookup_guarded(proc_t p, int fd, guardid_t guard,
+    struct guarded_fileproc **gfpp)
+{
+       struct fileproc *fp;
+       int error;
+
+       if ((error = fp_lookup(p, fd, &fp, 1)) != 0)
+               return (error);
+       if (FILEPROC_TYPE(fp) != FTYPE_GUARDED) {
+               (void) fp_drop(p, fd, fp, 1);
+               return (EINVAL);
+       }
+       struct guarded_fileproc *gfp = FP_TO_GFP(fp);
+
+       if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic)
+               panic("%s: corrupt fp %p", __func__, fp);
+
+       if (guard != gfp->gf_guard) {
+               (void) fp_drop(p, fd, fp, 1);
+               return (EPERM); /* *not* a mismatch exception */
+       }
+       if (gfpp)
+               *gfpp = gfp;
+       return (0);
+}
+
+/*
+ * Expected use pattern:
+ *
+ * if (FP_ISGUARDED(fp, GUARD_CLOSE)) {
+ *     error = fp_guard_exception(p, fd, fp, kGUARD_EXC_CLOSE);
+ *      proc_fdunlock(p);
+ *      return (error);
+ * }
+ */
+
+int
+fp_isguarded(struct fileproc *fp, u_int attrs)
+{
+       if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) {
+               struct guarded_fileproc *gfp = FP_TO_GFP(fp);
+
+               if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic)
+                       panic("%s: corrupt gfp %p flags %x",
+                           __func__, gfp, fp->f_flags);
+               return ((attrs & gfp->gf_attrs) ? 1 : 0);
+       }
+       return (0);
+}
+
+extern char *proc_name_address(void *p);
+
+int
+fp_guard_exception(proc_t p, int fd, struct fileproc *fp, u_int code)
+{
+       if (FILEPROC_TYPE(fp) != FTYPE_GUARDED)
+               panic("%s corrupt fp %p flags %x", __func__, fp, fp->f_flags);
+
+       struct guarded_fileproc *gfp = FP_TO_GFP(fp);
+
+       /* all gfd fields protected via proc_fdlock() */
+       proc_fdlock_assert(p, LCK_MTX_ASSERT_OWNED);
+
+       if (NULL == gfp->gf_thread) {
+               thread_t t = current_thread();
+               gfp->gf_thread = t;
+               gfp->gf_exc_fd = fd;
+               gfp->gf_exc_code = code;
+
+               /*
+                * This thread was the first to attempt the
+                * operation that violated the guard on this fd;
+                * generate an exception.
+                */
+               printf("%s: guarded fd exception: "
+                   "fd %d code 0x%x guard 0x%llx\n",
+                   proc_name_address(p), gfp->gf_exc_fd,
+                   gfp->gf_exc_code, gfp->gf_guard);
+
+               thread_guard_violation(t, GUARD_TYPE_FD);
+       } else {
+               /*
+                * We already recorded a violation on this fd for a
+                * different thread, so posting an exception is
+                * already in progress.  We could pause for a bit
+                * and check again, or we could panic (though that seems
+                * heavy handed), or we could just press on with the
+                * error return alone.  For now, resort to printf.
+                */
+               printf("%s: guarded fd exception+: "
+                   "fd %d code 0x%x guard 0x%llx\n",
+                   proc_name_address(p), gfp->gf_exc_fd,
+                   gfp->gf_exc_code, gfp->gf_guard);
+       }
+
+       return (EPERM);
+}
+
+/*
+ * (Invoked before returning to userland from the syscall handler.)
+ */
+void
+fd_guard_ast(thread_t t)
+{
+       proc_t p = current_proc();
+       struct filedesc *fdp = p->p_fd;
+       int i;
+
+       proc_fdlock(p);
+       for (i = fdp->fd_lastfile; i >= 0; i--) {
+               struct fileproc *fp = fdp->fd_ofiles[i];
+
+               if (fp == NULL ||
+                   FILEPROC_TYPE(fp) != FTYPE_GUARDED)
+                       continue;
+
+               struct guarded_fileproc *gfp = FP_TO_GFP(fp);
+
+               if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic)
+                       panic("%s: corrupt gfp %p flags %x",
+                           __func__, gfp, fp->f_flags);
+
+               if (gfp->gf_thread == t) {
+                       mach_exception_data_type_t code, subcode;
+
+                       gfp->gf_thread = NULL;
+
+                       /*
+                        * EXC_GUARD exception code namespace.
+                        *
+                        * code:
+                        * +-------------------------------------------------+
+                        * | [63:61] guard type | [60:0] guard-specific data |
+                        * +-------------------------------------------------+
+                        *
+                        * subcode:
+                        * +-------------------------------------------------+
+                        * |       [63:0] guard-specific data                |
+                        * +-------------------------------------------------+
+                        *
+                        * At the moment, we have just one guard type: file
+                        * descriptor guards.
+                        *
+                        * File descriptor guards use the exception codes like
+                        * so:
+                        *
+                        * code:                         
+                        * +--------------------------------------------------+
+                        * |[63:61] GUARD_TYPE_FD | [60:32] flavor | [31:0] fd|
+                        * +--------------------------------------------------+
+                        *
+                        * subcode:
+                        * +--------------------------------------------------+
+                        * |       [63:0] guard value                         |
+                        * +--------------------------------------------------+
+                        */
+                       code = (((uint64_t)GUARD_TYPE_FD) << 61) |
+                              (((uint64_t)gfp->gf_exc_code) << 32) |
+                              ((uint64_t)gfp->gf_exc_fd);
+                       subcode = gfp->gf_guard;
+                       proc_fdunlock(p);
+
+                       (void) task_exception_notify(EXC_GUARD, code, subcode);
+                       psignal(p, SIGKILL);
+
+                       return;
+               }
+       }
+       proc_fdunlock(p);
+}
+
+/*
+ * Experimental guarded file descriptor SPIs
+ */
+
+/*
+ * int guarded_open_np(const char *pathname, int flags,
+ *     const guardid_t *guard, u_int guardflags, ...);
+ *
+ * In this initial implementation, GUARD_DUP must be specified.
+ * GUARD_CLOSE, GUARD_SOCKET_IPC and GUARD_FILEPORT are optional.
+ *
+ * If GUARD_DUP wasn't specified, then we'd have to do the (extra) work
+ * to allow dup-ing a descriptor to inherit the guard onto the new
+ * descriptor.  (Perhaps GUARD_DUP behaviours should just always be true
+ * for a guarded fd?  Or, more sanely, all the dup operations should
+ * just always propagate the guard?)
+ *
+ * Guarded descriptors are always close-on-exec, and GUARD_CLOSE
+ * requires close-on-fork; O_CLOEXEC must be set in flags.
+ * This setting is immutable; attempts to clear the flag will
+ * cause a guard exception.
+ */
+int
+guarded_open_np(proc_t p, struct guarded_open_np_args *uap, int32_t *retval)
+{
+       if ((uap->flags & O_CLOEXEC) == 0)
+               return (EINVAL);
+
+#define GUARD_REQUIRED (GUARD_DUP)
+#define GUARD_ALL      (GUARD_REQUIRED |       \
+                       (GUARD_CLOSE | GUARD_SOCKET_IPC | GUARD_FILEPORT))
+
+       if (((uap->guardflags & GUARD_REQUIRED) != GUARD_REQUIRED) ||
+           ((uap->guardflags & ~GUARD_ALL) != 0))
+               return (EINVAL);
+
+       int error;
+       struct gfp_crarg crarg = {
+               .gca_attrs = uap->guardflags
+       };
+
+       if ((error = copyin(uap->guard,
+           &(crarg.gca_guard), sizeof (crarg.gca_guard))) != 0)
+               return (error);
+
+       /*
+        * Disallow certain guard values -- is zero enough?
+        */
+       if (crarg.gca_guard == 0)
+               return (EINVAL);
+
+       struct filedesc *fdp = p->p_fd;
+       struct vnode_attr va;
+       struct nameidata nd;
+       vfs_context_t ctx = vfs_context_current();
+       int cmode;
+
+       VATTR_INIT(&va);
+       cmode = ((uap->mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT;
+       VATTR_SET(&va, va_mode, cmode & ACCESSPERMS);
+
+       NDINIT(&nd, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1, UIO_USERSPACE,
+              uap->path, ctx);
+
+       return (open1(ctx, &nd, uap->flags | O_CLOFORK, &va,
+           guarded_fileproc_alloc_init, &crarg, retval));
+}
+
+/*
+ * int guarded_kqueue_np(const guardid_t *guard, u_int guardflags);
+ *
+ * Create a guarded kqueue descriptor with guardid and guardflags.
+ *
+ * Same restrictions on guardflags as for guarded_open_np().
+ * All kqueues are -always- close-on-exec and close-on-fork by themselves.
+ *
+ * XXX Is it ever sensible to allow a kqueue fd (guarded or not) to
+ *     be sent to another process via a fileport or socket?
+ */
+int
+guarded_kqueue_np(proc_t p, struct guarded_kqueue_np_args *uap, int32_t *retval)
+{
+       if (((uap->guardflags & GUARD_REQUIRED) != GUARD_REQUIRED) ||
+           ((uap->guardflags & ~GUARD_ALL) != 0))
+               return (EINVAL);
+
+       int error;
+       struct gfp_crarg crarg = {
+               .gca_attrs = uap->guardflags
+       };
+
+       if ((error = copyin(uap->guard,
+           &(crarg.gca_guard), sizeof (crarg.gca_guard))) != 0)
+               return (error);
+
+       if (crarg.gca_guard == 0)
+               return (EINVAL);
+
+       return (kqueue_body(p, guarded_fileproc_alloc_init, &crarg, retval));
+}
+
+/*
+ * int guarded_close_np(int fd, const guardid_t *guard);
+ */
+int
+guarded_close_np(proc_t p, struct guarded_close_np_args *uap,
+    __unused int32_t *retval)
+{
+       struct guarded_fileproc *gfp;
+       int fd = uap->fd;
+       int error;
+       guardid_t uguard;
+
+       AUDIT_SYSCLOSE(p, fd);
+
+       if ((error = copyin(uap->guard, &uguard, sizeof (uguard))) != 0)
+               return (error);
+
+       proc_fdlock(p);
+       if ((error = fp_lookup_guarded(p, fd, uguard, &gfp)) != 0) {
+               proc_fdunlock(p);
+               return (error);
+       }
+       error = close_internal_locked(p, fd, GFP_TO_FP(gfp), 0);
+       proc_fdunlock(p);
+       return (error);
+}
+
+/*
+ * int
+ * change_fdguard_np(int fd, const guardid_t *guard, u_int guardflags,
+ *    const guardid_t *nguard, u_int nguardflags, int *fdflagsp);
+ *
+ * Given a file descriptor, atomically exchange <guard, guardflags> for
+ * a new guard <nguard, nguardflags>, returning the previous fd
+ * flags (see fcntl:F_SETFD) in *fdflagsp.
+ *
+ * This syscall can be used to either (a) add a new guard to an existing
+ * unguarded file descriptor (b) remove the old guard from an existing
+ * guarded file descriptor or (c) change the guard (guardid and/or
+ * guardflags) on a guarded file descriptor.
+ *
+ * If 'guard' is NULL, fd must be unguarded at entry. If the call completes
+ * successfully the fd will be guarded with <nguard, nguardflags>.
+ *
+ * Guarding a file descriptor has some side-effects on the "fdflags"
+ * associated with the descriptor - in particular FD_CLOEXEC is
+ * forced ON unconditionally, and FD_CLOFORK is forced ON by GUARD_CLOSE.
+ * Callers who wish to subsequently restore the state of the fd should save
+ * the value of *fdflagsp after a successful invocation.
+ *
+ * If 'nguard' is NULL, fd must be guarded at entry, <guard, guardflags>
+ * must match with what's already guarding the descriptor, and the
+ * result will be to completely remove the guard.  Note also that the
+ * fdflags are copied to the descriptor from the incoming *fdflagsp argument. 
+ *
+ * If the descriptor is guarded, and neither 'guard' nor 'nguard' is NULL
+ * and <guard, guardflags> matches what's already guarding the descriptor,
+ * then <nguard, nguardflags> becomes the new guard.  In this case, even if
+ * the GUARD_CLOSE flag is being cleared, it is still possible to continue
+ * to keep FD_CLOFORK on the descriptor by passing FD_CLOFORK via fdflagsp.
+ *
+ * Example 1: Guard an unguarded descriptor during a set of operations,
+ * then restore the original state of the descriptor.
+ *
+ * int sav_flags = 0;
+ * change_fdguard_np(fd, NULL, 0, &myguard, GUARD_CLOSE, &sav_flags);
+ * // do things with now guarded 'fd'
+ * change_fdguard_np(fd, &myguard, GUARD_CLOSE, NULL, 0, &sav_flags);
+ * // fd now unguarded.
+ *
+ * Example 2: Change the guard of a guarded descriptor during a set of
+ * operations, then restore the original state of the descriptor.
+ *
+ * int sav_flags = (gdflags & GUARD_CLOSE) ? FD_CLOFORK : 0;
+ * change_fdguard_np(fd, &gd, gdflags, &myguard, GUARD_CLOSE, &sav_flags);
+ * // do things with 'fd' with a different guard
+ * change_fdguard_np(fd, &myg, GUARD_CLOSE, &gd, gdflags, &sav_flags);
+ * // back to original guarded state
+ */
+
+#define FDFLAGS_GET(p, fd) (*fdflags(p, fd) & (UF_EXCLOSE|UF_FORKCLOSE))
+#define FDFLAGS_SET(p, fd, bits) \
+          (*fdflags(p, fd) |= ((bits) & (UF_EXCLOSE|UF_FORKCLOSE)))
+#define FDFLAGS_CLR(p, fd, bits) \
+          (*fdflags(p, fd) &= ~((bits) & (UF_EXCLOSE|UF_FORKCLOSE)))
+
+int
+change_fdguard_np(proc_t p, struct change_fdguard_np_args *uap,
+    __unused int32_t *retval)
+{
+       struct fileproc *fp;
+       int fd = uap->fd;
+       int error;
+       guardid_t oldg = 0, newg = 0;
+       int nfdflags = 0;
+
+       if (0 != uap->guard &&
+           0 != (error = copyin(uap->guard, &oldg, sizeof (oldg))))
+               return (error); /* can't copyin current guard */
+
+       if (0 != uap->nguard &&
+           0 != (error = copyin(uap->nguard, &newg, sizeof (newg))))
+               return (error); /* can't copyin new guard */
+
+       if (0 != uap->fdflagsp &&
+           0 != (error = copyin(uap->fdflagsp, &nfdflags, sizeof (nfdflags))))
+               return (error); /* can't copyin new fdflags */
+           
+       proc_fdlock(p);
+restart:
+       if ((error = fp_lookup(p, fd, &fp, 1)) != 0) {
+               proc_fdunlock(p);
+               return (error);
+       }
+
+       if (0 != uap->fdflagsp) {
+               int ofdflags = FDFLAGS_GET(p, fd);
+               int ofl = ((ofdflags & UF_EXCLOSE) ? FD_CLOEXEC : 0) |
+                       ((ofdflags & UF_FORKCLOSE) ? FD_CLOFORK : 0);
+               proc_fdunlock(p);
+               if (0 != (error = copyout(&ofl, uap->fdflagsp, sizeof (ofl)))) {
+                       proc_fdlock(p);
+                       goto dropout; /* can't copyout old fdflags */
+               }
+               proc_fdlock(p);
+       }
+
+       if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) {
+               if (0 == uap->guard || 0 == uap->guardflags)
+                       error = EINVAL; /* missing guard! */
+               else if (0 == oldg)
+                       error = EPERM; /* guardids cannot be zero */
+       } else {
+               if (0 != uap->guard || 0 != uap->guardflags)
+                       error = EINVAL; /* guard provided, but none needed! */
+       }
+
+       if (0 != error)
+               goto dropout;
+
+       if (0 != uap->nguard) {
+               /*
+                * There's a new guard in town.
+                */
+               if (0 == newg)
+                       error = EINVAL; /* guards cannot contain zero */
+               else if (0 == uap->nguardflags)
+                       error = EINVAL; /* attributes cannot be zero */
+               else if (((uap->nguardflags & GUARD_REQUIRED) != GUARD_REQUIRED) ||
+                   ((uap->guardflags & ~GUARD_ALL) != 0))
+                       error = EINVAL; /* must have valid attributes too */
+            
+               if (0 != error)
+                       goto dropout;
+
+               if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) {
+                       /*
+                        * Replace old guard with new guard
+                        */
+                       struct guarded_fileproc *gfp = FP_TO_GFP(fp);
+
+                       if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic)
+                               panic("%s: corrupt gfp %p flags %x",
+                                     __func__, gfp, fp->f_flags);
+
+                       if (oldg == gfp->gf_guard &&
+                           uap->guardflags == gfp->gf_attrs) {
+                               /*
+                                * Must match existing guard + attributes
+                                * before we'll swap them to new ones, managing
+                                * fdflags "side-effects" as we go.   Note that
+                                * userland can request FD_CLOFORK semantics.
+                                */
+                               if (gfp->gf_attrs & GUARD_CLOSE)
+                                       FDFLAGS_CLR(p, fd, UF_FORKCLOSE);
+                               gfp->gf_guard = newg;
+                               gfp->gf_attrs = uap->nguardflags;
+                               if (gfp->gf_attrs & GUARD_CLOSE)
+                                       FDFLAGS_SET(p, fd, UF_FORKCLOSE);
+                               FDFLAGS_SET(p, fd,
+                                   (nfdflags & FD_CLOFORK) ? UF_FORKCLOSE : 0);
+                       } else {
+                               error = EPERM;
+                       }
+                       goto dropout;
+               } else {
+                       /*
+                        * Add a guard to a previously unguarded descriptor
+                        */
+                       switch (FILEGLOB_DTYPE(fp->f_fglob)) {
+                       case DTYPE_VNODE:
+                       case DTYPE_PIPE:
+                       case DTYPE_SOCKET:
+                       case DTYPE_KQUEUE:
+                               break;
+                       default:
+                               error = ENOTSUP;
+                               goto dropout;
+                       }
+
+                       proc_fdunlock(p);
+
+                       struct gfp_crarg crarg = {
+                               .gca_guard = newg,
+                               .gca_attrs = uap->nguardflags
+                       };
+                       struct fileproc *nfp =
+                               guarded_fileproc_alloc_init(&crarg);
+
+                       proc_fdlock(p);
+
+                       switch (error = fp_tryswap(p, fd, nfp)) {
+                               struct guarded_fileproc *gfp;
+
+                       case 0: /* guarded-ness comes with side-effects */
+                               gfp = FP_TO_GFP(nfp);
+                               if (gfp->gf_attrs & GUARD_CLOSE)
+                                       FDFLAGS_SET(p, fd, UF_FORKCLOSE);
+                               FDFLAGS_SET(p, fd, UF_EXCLOSE);
+                               (void) fp_drop(p, fd, nfp, 1);
+                               fileproc_free(fp);
+                               break;
+                       case EKEEPLOOKING: /* f_iocount indicates a collision */
+                               (void) fp_drop(p, fd, fp, 1);
+                               fileproc_free(nfp);
+                               goto restart;
+                       default:
+                               (void) fp_drop(p, fd, fp, 1);
+                               fileproc_free(nfp);
+                               break;
+                       }
+                       proc_fdunlock(p);
+                       return (error);
+               }
+       } else {
+               /*
+                * No new guard.
+                */
+               if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) {
+                       /*
+                        * Remove the guard altogether.
+                        */
+                       struct guarded_fileproc *gfp = FP_TO_GFP(fp);
+
+                       if (0 != uap->nguardflags) {
+                               error = EINVAL;
+                               goto dropout;
+                       }
+
+                       if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic)
+                               panic("%s: corrupt gfp %p flags %x",
+                                     __func__, gfp, fp->f_flags);
+
+                       if (oldg != gfp->gf_guard ||
+                           uap->guardflags != gfp->gf_attrs) {
+                               error = EPERM;
+                               goto dropout;
+                       }
+
+                       proc_fdunlock(p);
+                       struct fileproc *nfp = fileproc_alloc_init(NULL);
+                       proc_fdlock(p);
+
+                       switch (error = fp_tryswap(p, fd, nfp)) {
+                       case 0: /* undo side-effects of guarded-ness */
+                               FDFLAGS_CLR(p, fd, UF_FORKCLOSE | UF_EXCLOSE);
+                               FDFLAGS_SET(p, fd,
+                                   (nfdflags & FD_CLOFORK) ? UF_FORKCLOSE : 0);
+                               FDFLAGS_SET(p, fd,
+                                   (nfdflags & FD_CLOEXEC) ? UF_EXCLOSE : 0);
+                               (void) fp_drop(p, fd, nfp, 1);
+                               fileproc_free(fp);
+                               break;
+                       case EKEEPLOOKING: /* f_iocount indicates collision */
+                               (void) fp_drop(p, fd, fp, 1);
+                               fileproc_free(nfp);
+                               goto restart;
+                       default:
+                               (void) fp_drop(p, fd, fp, 1);
+                               fileproc_free(nfp);
+                               break;
+                       }
+                       proc_fdunlock(p);
+                       return (error);
+               } else {
+                       /*
+                        * Not already guarded, and no new guard?
+                        */
+                       error = EINVAL;
+               }
+       }
+
+dropout:
+       (void) fp_drop(p, fd, fp, 1);
+       proc_fdunlock(p);
+       return (error);
+}
+