X-Git-Url: https://git.saurik.com/apple/xnu.git/blobdiff_plain/39236c6e673c41db228275375ab7fdb0f837b292..eb6b6ca394357805f2bdba989abae309f718b4d8:/bsd/kern/kern_guarded.c diff --git a/bsd/kern/kern_guarded.c b/bsd/kern/kern_guarded.c index 5c175c7bb..c78c64673 100644 --- a/bsd/kern/kern_guarded.c +++ b/bsd/kern/kern_guarded.c @@ -1,8 +1,8 @@ /* - * Copyright (c) 2012 Apple Inc. All rights reserved. + * Copyright (c) 2018 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 @@ -11,10 +11,10 @@ * 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, @@ -22,7 +22,7 @@ * 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@ */ @@ -31,19 +31,46 @@ #include #include #include +#include #include #include #include #include +#include +#include +#include #include #include +#include +#include +#include +#include +#include +#include +#if CONFIG_MACF && CONFIG_VNGUARD +#include +#include +#include +#include +#include +#include +#endif + + +#define f_flag f_fglob->fg_flag +#define f_type f_fglob->fg_ops->fo_type +extern int dofilewrite(vfs_context_t ctx, struct fileproc *fp, + user_addr_t bufp, user_size_t nbyte, off_t offset, + int flags, user_ssize_t *retval ); +extern int wr_uio(struct proc *p, struct fileproc *fp, uio_t uio, user_ssize_t *retval); /* * 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); + mach_exception_data_type_t code, mach_exception_data_type_t subcode); +kern_return_t task_violated_guard(mach_exception_code_t, mach_exception_subcode_t, void *); /* * Most fd's have an underlying fileproc struct; but some may be @@ -60,42 +87,48 @@ kern_return_t task_exception_notify(exception_type_t exception, 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; + u_int gf_magic; + u_int gf_attrs; + guardid_t gf_guard; }; -const size_t sizeof_guarded_fileproc = sizeof (struct guarded_fileproc); +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 FP_TO_GFP(fp) ((struct guarded_fileproc *)(fp)) +#define GFP_TO_FP(gfp) (&(gfp)->gf_fileproc) -#define GUARDED_FILEPROC_MAGIC 0x29083 +#define GUARDED_FILEPROC_MAGIC 0x29083 struct gfp_crarg { guardid_t gca_guard; u_int gca_attrs; }; +#ifdef OS_REFCNT_DEBUG +extern struct os_refgrp f_iocount_refgrp; +#endif + 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); + if ((gfp = kalloc(sizeof(*gfp))) == NULL) { + return NULL; + } + + bzero(gfp, sizeof(*gfp)); + + struct fileproc *fp = &gfp->gf_fileproc; + os_ref_init(&fp->f_iocount, &f_iocount_refgrp); + fp->f_flags = FTYPE_GUARDED; - 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)); + return GFP_TO_FP(gfp); } void @@ -104,46 +137,50 @@ 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) + GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { panic("%s: corrupt fp %p flags %x", __func__, fp, fp->f_flags); + } - kfree(gfp, sizeof (*gfp)); + kfree(gfp, sizeof(*gfp)); } static int fp_lookup_guarded(proc_t p, int fd, guardid_t guard, - struct guarded_fileproc **gfpp) + struct guarded_fileproc **gfpp, int locked) { struct fileproc *fp; int error; - if ((error = fp_lookup(p, fd, &fp, 1)) != 0) - return (error); + if ((error = fp_lookup(p, fd, &fp, locked)) != 0) { + return error; + } if (FILEPROC_TYPE(fp) != FTYPE_GUARDED) { - (void) fp_drop(p, fd, fp, 1); - return (EINVAL); + (void) fp_drop(p, fd, fp, locked); + return EINVAL; } struct guarded_fileproc *gfp = FP_TO_GFP(fp); - if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) + 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 */ + (void) fp_drop(p, fd, fp, locked); + return EPERM; /* *not* a mismatch exception */ } - if (gfpp) + if (gfpp) { *gfpp = gfp; - return (0); + } + return 0; } /* * Expected use pattern: * * if (FP_ISGUARDED(fp, GUARD_CLOSE)) { - * error = fp_guard_exception(p, fd, fp, kGUARD_EXC_CLOSE); + * error = fp_guard_exception(p, fd, fp, kGUARD_EXC_CLOSE); * proc_fdunlock(p); - * return (error); + * return error; * } */ @@ -153,133 +190,51 @@ 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) + 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 (attrs & gfp->gf_attrs) == attrs; } - return (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) +fp_guard_exception(proc_t p, int fd, struct fileproc *fp, u_int flavor) { - if (FILEPROC_TYPE(fp) != FTYPE_GUARDED) + 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); - } + mach_exception_code_t code = 0; + EXC_GUARD_ENCODE_TYPE(code, GUARD_TYPE_FD); + EXC_GUARD_ENCODE_FLAVOR(code, flavor); + EXC_GUARD_ENCODE_TARGET(code, fd); + mach_exception_subcode_t subcode = gfp->gf_guard; - return (EPERM); + thread_t t = current_thread(); + thread_guard_violation(t, code, subcode, TRUE); + return EPERM; } /* * (Invoked before returning to userland from the syscall handler.) */ void -fd_guard_ast(thread_t t) +fd_guard_ast( + thread_t __unused t, + mach_exception_code_t code, + mach_exception_subcode_t subcode) { + task_exception_notify(EXC_GUARD, code, subcode); 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); + psignal(p, SIGKILL); } /* @@ -303,20 +258,26 @@ fd_guard_ast(thread_t t) * 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. + * + * XXX It's somewhat broken that change_fdguard_np() can completely + * remove the guard and thus revoke down the immutability + * promises above. Ick. */ int guarded_open_np(proc_t p, struct guarded_open_np_args *uap, int32_t *retval) { - if ((uap->flags & O_CLOEXEC) == 0) - return (EINVAL); + 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)) +#define GUARD_ALL (GUARD_REQUIRED | \ + (GUARD_CLOSE | GUARD_SOCKET_IPC | GUARD_FILEPORT | GUARD_WRITE)) if (((uap->guardflags & GUARD_REQUIRED) != GUARD_REQUIRED) || - ((uap->guardflags & ~GUARD_ALL) != 0)) - return (EINVAL); + ((uap->guardflags & ~GUARD_ALL) != 0)) { + return EINVAL; + } int error; struct gfp_crarg crarg = { @@ -324,14 +285,16 @@ guarded_open_np(proc_t p, struct guarded_open_np_args *uap, int32_t *retval) }; if ((error = copyin(uap->guard, - &(crarg.gca_guard), sizeof (crarg.gca_guard))) != 0) - return (error); + &(crarg.gca_guard), sizeof(crarg.gca_guard))) != 0) { + return error; + } /* * Disallow certain guard values -- is zero enough? */ - if (crarg.gca_guard == 0) - return (EINVAL); + if (crarg.gca_guard == 0) { + return EINVAL; + } struct filedesc *fdp = p->p_fd; struct vnode_attr va; @@ -344,10 +307,87 @@ guarded_open_np(proc_t p, struct guarded_open_np_args *uap, int32_t *retval) VATTR_SET(&va, va_mode, cmode & ACCESSPERMS); NDINIT(&nd, LOOKUP, OP_OPEN, FOLLOW | AUDITVNPATH1, UIO_USERSPACE, - uap->path, ctx); + uap->path, ctx); - return (open1(ctx, &nd, uap->flags | O_CLOFORK, &va, - guarded_fileproc_alloc_init, &crarg, retval)); + return open1(ctx, &nd, uap->flags | O_CLOFORK, &va, + guarded_fileproc_alloc_init, &crarg, retval); +} + +/* + * int guarded_open_dprotected_np(const char *pathname, int flags, + * const guardid_t *guard, u_int guardflags, int dpclass, int dpflags, ...); + * + * This SPI is extension of guarded_open_np() to include dataprotection class on creation + * in "dpclass" and dataprotection flags 'dpflags'. Otherwise behaviors are same as in + * guarded_open_np() + */ +int +guarded_open_dprotected_np(proc_t p, struct guarded_open_dprotected_np_args *uap, int32_t *retval) +{ + if ((uap->flags & O_CLOEXEC) == 0) { + return EINVAL; + } + + 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); + + /* + * Initialize the extra fields in vnode_attr to pass down dataprotection + * extra fields. + * 1. target cprotect class. + * 2. set a flag to mark it as requiring open-raw-encrypted semantics. + */ + if (uap->flags & O_CREAT) { + VATTR_SET(&va, va_dataprotect_class, uap->dpclass); + } + + if (uap->dpflags & (O_DP_GETRAWENCRYPTED | O_DP_GETRAWUNENCRYPTED)) { + if (uap->flags & (O_RDWR | O_WRONLY)) { + /* Not allowed to write raw encrypted bytes */ + return EINVAL; + } + if (uap->dpflags & O_DP_GETRAWENCRYPTED) { + VATTR_SET(&va, va_dataprotect_flags, VA_DP_RAWENCRYPTED); + } + if (uap->dpflags & O_DP_GETRAWUNENCRYPTED) { + VATTR_SET(&va, va_dataprotect_flags, VA_DP_RAWUNENCRYPTED); + } + } + + return open1(ctx, &nd, uap->flags | O_CLOFORK, &va, + guarded_fileproc_alloc_init, &crarg, retval); } /* @@ -356,17 +396,16 @@ guarded_open_np(proc_t p, struct guarded_open_np_args *uap, int32_t *retval) * 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? + * All kqueues are -always- close-on-exec and close-on-fork by themselves + * and are not sendable. */ 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); + ((uap->guardflags & ~GUARD_ALL) != 0)) { + return EINVAL; + } int error; struct gfp_crarg crarg = { @@ -374,13 +413,15 @@ guarded_kqueue_np(proc_t p, struct guarded_kqueue_np_args *uap, int32_t *retval) }; if ((error = copyin(uap->guard, - &(crarg.gca_guard), sizeof (crarg.gca_guard))) != 0) - return (error); + &(crarg.gca_guard), sizeof(crarg.gca_guard))) != 0) { + return error; + } - if (crarg.gca_guard == 0) - return (EINVAL); + if (crarg.gca_guard == 0) { + return EINVAL; + } - return (kqueue_body(p, guarded_fileproc_alloc_init, &crarg, retval)); + return kqueue_internal(p, guarded_fileproc_alloc_init, &crarg, retval); } /* @@ -397,17 +438,18 @@ guarded_close_np(proc_t p, struct guarded_close_np_args *uap, AUDIT_SYSCLOSE(p, fd); - if ((error = copyin(uap->guard, &uguard, sizeof (uguard))) != 0) - return (error); + if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { + return error; + } proc_fdlock(p); - if ((error = fp_lookup_guarded(p, fd, uguard, &gfp)) != 0) { + if ((error = fp_lookup_guarded(p, fd, uguard, &gfp, 1)) != 0) { proc_fdunlock(p); - return (error); + return error; } error = close_internal_locked(p, fd, GFP_TO_FP(gfp), 0); proc_fdunlock(p); - return (error); + return error; } /* @@ -436,7 +478,7 @@ guarded_close_np(proc_t p, struct guarded_close_np_args *uap, * If 'nguard' is NULL, fd must be guarded at entry, * 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. + * fdflags are copied to the descriptor from the incoming *fdflagsp argument. * * If the descriptor is guarded, and neither 'guard' nor 'nguard' is NULL * and matches what's already guarding the descriptor, @@ -444,6 +486,9 @@ guarded_close_np(proc_t p, struct guarded_close_np_args *uap, * 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. * + * (File descriptors whose underlying fileglobs are marked FG_CONFINED are + * still close-on-fork, regardless of the setting of FD_CLOFORK.) + * * Example 1: Guard an unguarded descriptor during a set of operations, * then restore the original state of the descriptor. * @@ -461,14 +506,10 @@ guarded_close_np(proc_t p, struct guarded_close_np_args *uap, * // do things with 'fd' with a different guard * change_fdguard_np(fd, &myg, GUARD_CLOSE, &gd, gdflags, &sav_flags); * // back to original guarded state + * + * XXX This SPI is too much of a chainsaw and should be revised. */ -#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) @@ -480,30 +521,30 @@ change_fdguard_np(proc_t p, struct change_fdguard_np_args *uap, int nfdflags = 0; if (0 != uap->guard && - 0 != (error = copyin(uap->guard, &oldg, sizeof (oldg)))) - return (error); /* can't copyin current 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 */ - + 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 */ - + 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); + 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); + ((ofdflags & UF_FORKCLOSE) ? FD_CLOFORK : 0); proc_fdunlock(p); - if (0 != (error = copyout(&ofl, uap->fdflagsp, sizeof (ofl)))) { + if (0 != (error = copyout(&ofl, uap->fdflagsp, sizeof(ofl)))) { proc_fdlock(p); goto dropout; /* can't copyout old fdflags */ } @@ -511,32 +552,34 @@ restart: } if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) { - if (0 == uap->guard || 0 == uap->guardflags) + if (0 == uap->guard || 0 == uap->guardflags) { error = EINVAL; /* missing guard! */ - else if (0 == oldg) + } else if (0 == oldg) { error = EPERM; /* guardids cannot be zero */ + } } else { - if (0 != uap->guard || 0 != uap->guardflags) + if (0 != uap->guard || 0 != uap->guardflags) { error = EINVAL; /* guard provided, but none needed! */ + } } - if (0 != error) + if (0 != error) { goto dropout; + } if (0 != uap->nguard) { /* * There's a new guard in town. */ - if (0 == newg) + 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)) + } else if (((uap->nguardflags & GUARD_REQUIRED) != GUARD_REQUIRED) || + ((uap->nguardflags & ~GUARD_ALL) != 0)) { error = EINVAL; /* must have valid attributes too */ - - if (0 != error) + } + if (0 != error) { goto dropout; + } if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) { /* @@ -544,9 +587,10 @@ restart: */ struct guarded_fileproc *gfp = FP_TO_GFP(fp); - if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) + if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { panic("%s: corrupt gfp %p flags %x", - __func__, gfp, fp->f_flags); + __func__, gfp, fp->f_flags); + } if (oldg == gfp->gf_guard && uap->guardflags == gfp->gf_attrs) { @@ -556,14 +600,17 @@ restart: * fdflags "side-effects" as we go. Note that * userland can request FD_CLOFORK semantics. */ - if (gfp->gf_attrs & GUARD_CLOSE) + 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) + if (gfp->gf_attrs & GUARD_CLOSE) { FDFLAGS_SET(p, fd, UF_FORKCLOSE); + } FDFLAGS_SET(p, fd, (nfdflags & FD_CLOFORK) ? UF_FORKCLOSE : 0); + /* FG_CONFINED enforced regardless */ } else { error = EPERM; } @@ -577,6 +624,7 @@ restart: case DTYPE_PIPE: case DTYPE_SOCKET: case DTYPE_KQUEUE: + case DTYPE_NETPOLICY: break; default: error = ENOTSUP; @@ -590,20 +638,20 @@ restart: .gca_attrs = uap->nguardflags }; struct fileproc *nfp = - guarded_fileproc_alloc_init(&crarg); + guarded_fileproc_alloc_init(&crarg); + struct guarded_fileproc *gfp; proc_fdlock(p); switch (error = fp_tryswap(p, fd, nfp)) { - struct guarded_fileproc *gfp; - - case 0: /* guarded-ness comes with side-effects */ + case 0: /* success; guarded-ness comes with side-effects */ + fp = NULL; gfp = FP_TO_GFP(nfp); - if (gfp->gf_attrs & GUARD_CLOSE) + 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); @@ -615,7 +663,7 @@ restart: break; } proc_fdunlock(p); - return (error); + return error; } } else { /* @@ -632,9 +680,10 @@ restart: goto dropout; } - if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) + if (GUARDED_FILEPROC_MAGIC != gfp->gf_magic) { panic("%s: corrupt gfp %p flags %x", - __func__, gfp, fp->f_flags); + __func__, gfp, fp->f_flags); + } if (oldg != gfp->gf_guard || uap->guardflags != gfp->gf_attrs) { @@ -647,14 +696,15 @@ restart: proc_fdlock(p); switch (error = fp_tryswap(p, fd, nfp)) { - case 0: /* undo side-effects of guarded-ness */ + case 0: /* success; undo side-effects of guarded-ness */ + fp = NULL; FDFLAGS_CLR(p, fd, UF_FORKCLOSE | UF_EXCLOSE); FDFLAGS_SET(p, fd, (nfdflags & FD_CLOFORK) ? UF_FORKCLOSE : 0); + /* FG_CONFINED enforced regardless */ 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); @@ -666,7 +716,7 @@ restart: break; } proc_fdunlock(p); - return (error); + return error; } else { /* * Not already guarded, and no new guard? @@ -678,6 +728,1007 @@ restart: dropout: (void) fp_drop(p, fd, fp, 1); proc_fdunlock(p); - return (error); + return error; +} + +/* + * user_ssize_t guarded_write_np(int fd, const guardid_t *guard, + * user_addr_t cbuf, user_ssize_t nbyte); + * + * Initial implementation of guarded writes. + */ +int +guarded_write_np(struct proc *p, struct guarded_write_np_args *uap, user_ssize_t *retval) +{ + int error; + int fd = uap->fd; + guardid_t uguard; + struct fileproc *fp; + struct guarded_fileproc *gfp; + bool wrote_some = false; + + AUDIT_ARG(fd, fd); + + if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { + return error; + } + + error = fp_lookup_guarded(p, fd, uguard, &gfp, 0); + if (error) { + return error; + } + + fp = GFP_TO_FP(gfp); + if ((fp->f_flag & FWRITE) == 0) { + error = EBADF; + } else { + struct vfs_context context = *(vfs_context_current()); + context.vc_ucred = fp->f_fglob->fg_cred; + + error = dofilewrite(&context, fp, uap->cbuf, uap->nbyte, + (off_t)-1, 0, retval); + wrote_some = *retval > 0; + } + if (wrote_some) { + fp_drop_written(p, fd, fp); + } else { + fp_drop(p, fd, fp, 0); + } + return error; +} + +/* + * user_ssize_t guarded_pwrite_np(int fd, const guardid_t *guard, + * user_addr_t buf, user_size_t nbyte, off_t offset); + * + * Initial implementation of guarded pwrites. + */ +int +guarded_pwrite_np(struct proc *p, struct guarded_pwrite_np_args *uap, user_ssize_t *retval) +{ + struct fileproc *fp; + int error; + int fd = uap->fd; + vnode_t vp = (vnode_t)0; + guardid_t uguard; + struct guarded_fileproc *gfp; + bool wrote_some = false; + + AUDIT_ARG(fd, fd); + + if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { + return error; + } + + error = fp_lookup_guarded(p, fd, uguard, &gfp, 0); + if (error) { + return error; + } + + fp = GFP_TO_FP(gfp); + if ((fp->f_flag & FWRITE) == 0) { + error = EBADF; + } else { + struct vfs_context context = *vfs_context_current(); + context.vc_ucred = fp->f_fglob->fg_cred; + + if (fp->f_type != DTYPE_VNODE) { + error = ESPIPE; + goto errout; + } + vp = (vnode_t)fp->f_fglob->fg_data; + if (vnode_isfifo(vp)) { + error = ESPIPE; + goto errout; + } + if ((vp->v_flag & VISTTY)) { + error = ENXIO; + goto errout; + } + if (uap->offset == (off_t)-1) { + error = EINVAL; + goto errout; + } + + error = dofilewrite(&context, fp, uap->buf, uap->nbyte, + uap->offset, FOF_OFFSET, retval); + wrote_some = *retval > 0; + } +errout: + if (wrote_some) { + fp_drop_written(p, fd, fp); + } else { + fp_drop(p, fd, fp, 0); + } + + KERNEL_DEBUG_CONSTANT((BSDDBG_CODE(DBG_BSD_SC_EXTENDED_INFO, SYS_guarded_pwrite_np) | DBG_FUNC_NONE), + uap->fd, uap->nbyte, (unsigned int)((uap->offset >> 32)), (unsigned int)(uap->offset), 0); + + return error; +} + +/* + * user_ssize_t guarded_writev_np(int fd, const guardid_t *guard, + * struct iovec *iovp, u_int iovcnt); + * + * Initial implementation of guarded writev. + * + */ +int +guarded_writev_np(struct proc *p, struct guarded_writev_np_args *uap, user_ssize_t *retval) +{ + uio_t auio = NULL; + int error; + struct fileproc *fp; + struct user_iovec *iovp; + guardid_t uguard; + struct guarded_fileproc *gfp; + bool wrote_some = false; + + AUDIT_ARG(fd, uap->fd); + + /* Verify range bedfore calling uio_create() */ + if (uap->iovcnt <= 0 || uap->iovcnt > UIO_MAXIOV) { + return EINVAL; + } + + /* allocate a uio large enough to hold the number of iovecs passed */ + auio = uio_create(uap->iovcnt, 0, + (IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32), + UIO_WRITE); + + /* get location of iovecs within the uio. then copyin the iovecs from + * user space. + */ + iovp = uio_iovsaddr(auio); + if (iovp == NULL) { + error = ENOMEM; + goto ExitThisRoutine; + } + error = copyin_user_iovec_array(uap->iovp, + IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32, + uap->iovcnt, iovp); + if (error) { + goto ExitThisRoutine; + } + + /* finalize uio_t for use and do the IO + */ + error = uio_calculateresid(auio); + if (error) { + goto ExitThisRoutine; + } + + if ((error = copyin(uap->guard, &uguard, sizeof(uguard))) != 0) { + goto ExitThisRoutine; + } + + error = fp_lookup_guarded(p, uap->fd, uguard, &gfp, 0); + if (error) { + goto ExitThisRoutine; + } + + fp = GFP_TO_FP(gfp); + if ((fp->f_flag & FWRITE) == 0) { + error = EBADF; + } else { + error = wr_uio(p, fp, auio, retval); + wrote_some = *retval > 0; + } + + if (wrote_some) { + fp_drop_written(p, uap->fd, fp); + } else { + fp_drop(p, uap->fd, fp, 0); + } +ExitThisRoutine: + if (auio != NULL) { + uio_free(auio); + } + return error; +} + +/* + * int falloc_guarded(struct proc *p, struct fileproc **fp, int *fd, + * vfs_context_t ctx, const guardid_t *guard, u_int attrs); + * + * This SPI is the guarded variant of falloc(). It borrows the same + * restrictions as those used by the rest of the guarded_* routines. + */ +int +falloc_guarded(struct proc *p, struct fileproc **fp, int *fd, + vfs_context_t ctx, const guardid_t *guard, u_int attrs) +{ + struct gfp_crarg crarg; + + if (((attrs & GUARD_REQUIRED) != GUARD_REQUIRED) || + ((attrs & ~GUARD_ALL) != 0) || (*guard == 0)) { + return EINVAL; + } + + bzero(&crarg, sizeof(crarg)); + crarg.gca_guard = *guard; + crarg.gca_attrs = attrs; + + return falloc_withalloc(p, fp, fd, ctx, guarded_fileproc_alloc_init, + &crarg); +} + +#if CONFIG_MACF && CONFIG_VNGUARD + +/* + * Guarded vnodes + * + * Uses MAC hooks to guard operations on vnodes in the system. Given an fd, + * add data to the label on the fileglob and the vnode it points at. + * The data contains a pointer to the fileglob, the set of attributes to + * guard, a guard value for uniquification, and the pid of the process + * who set the guard up in the first place. + * + * The fd must have been opened read/write, and the underlying + * fileglob is FG_CONFINED so that there's no ambiguity about the + * owning process. + * + * When there's a callback for a vnode operation of interest (rename, unlink, + * etc.) check to see if the guard permits that operation, and if not + * take an action e.g. log a message or generate a crash report. + * + * The label is removed from the vnode and the fileglob when the fileglob + * is closed. + * + * The initial action to be taken can be specified by a boot arg (vnguard=0x42) + * and change via the "kern.vnguard.flags" sysctl. + */ + +struct vng_owner; + +struct vng_info { /* lives on the vnode label */ + guardid_t vgi_guard; + unsigned vgi_attrs; + TAILQ_HEAD(, vng_owner) vgi_owners; +}; + +struct vng_owner { /* lives on the fileglob label */ + proc_t vgo_p; + struct fileglob *vgo_fg; + struct vng_info *vgo_vgi; + TAILQ_ENTRY(vng_owner) vgo_link; +}; + +static struct vng_info * +new_vgi(unsigned attrs, guardid_t guard) +{ + struct vng_info *vgi = kalloc(sizeof(*vgi)); + vgi->vgi_guard = guard; + vgi->vgi_attrs = attrs; + TAILQ_INIT(&vgi->vgi_owners); + return vgi; +} + +static struct vng_owner * +new_vgo(proc_t p, struct fileglob *fg) +{ + struct vng_owner *vgo = kalloc(sizeof(*vgo)); + memset(vgo, 0, sizeof(*vgo)); + vgo->vgo_p = p; + vgo->vgo_fg = fg; + return vgo; +} + +static void +vgi_add_vgo(struct vng_info *vgi, struct vng_owner *vgo) +{ + vgo->vgo_vgi = vgi; + TAILQ_INSERT_HEAD(&vgi->vgi_owners, vgo, vgo_link); +} + +static boolean_t +vgi_remove_vgo(struct vng_info *vgi, struct vng_owner *vgo) +{ + TAILQ_REMOVE(&vgi->vgi_owners, vgo, vgo_link); + vgo->vgo_vgi = NULL; + return TAILQ_EMPTY(&vgi->vgi_owners); +} + +static void +free_vgi(struct vng_info *vgi) +{ + assert(TAILQ_EMPTY(&vgi->vgi_owners)); +#if DEVELOP || DEBUG + memset(vgi, 0xbeadfade, sizeof(*vgi)); +#endif + kfree(vgi, sizeof(*vgi)); +} + +static void +free_vgo(struct vng_owner *vgo) +{ +#if DEVELOP || DEBUG + memset(vgo, 0x2bedf1d0, sizeof(*vgo)); +#endif + kfree(vgo, sizeof(*vgo)); +} + +static int label_slot; +static lck_rw_t llock; +static lck_grp_t *llock_grp; + +static __inline void * +vng_lbl_get(struct label *label) +{ + lck_rw_assert(&llock, LCK_RW_ASSERT_HELD); + void *data; + if (NULL == label) { + data = NULL; + } else { + data = (void *)mac_label_get(label, label_slot); + } + return data; +} + +static __inline struct vng_info * +vng_lbl_get_withattr(struct label *label, unsigned attrmask) +{ + struct vng_info *vgi = vng_lbl_get(label); + assert(NULL == vgi || (vgi->vgi_attrs & ~VNG_ALL) == 0); + if (NULL != vgi && 0 == (vgi->vgi_attrs & attrmask)) { + vgi = NULL; + } + return vgi; +} + +static __inline void +vng_lbl_set(struct label *label, void *data) +{ + assert(NULL != label); + lck_rw_assert(&llock, LCK_RW_ASSERT_EXCLUSIVE); + mac_label_set(label, label_slot, (intptr_t)data); +} + +static int +vnguard_sysc_getguardattr(proc_t p, struct vnguard_getattr *vga) +{ + const int fd = vga->vga_fd; + + if (0 == vga->vga_guard) { + return EINVAL; + } + + int error; + struct fileproc *fp; + if (0 != (error = fp_lookup(p, fd, &fp, 0))) { + return error; + } + do { + struct fileglob *fg = fp->f_fglob; + if (FILEGLOB_DTYPE(fg) != DTYPE_VNODE) { + error = EBADF; + break; + } + struct vnode *vp = fg->fg_data; + if (!vnode_isreg(vp) || NULL == vp->v_mount) { + error = EBADF; + break; + } + error = vnode_getwithref(vp); + if (0 != error) { + break; + } + + vga->vga_attrs = 0; + + lck_rw_lock_shared(&llock); + + if (NULL != vp->v_label) { + const struct vng_info *vgi = vng_lbl_get(vp->v_label); + if (NULL != vgi) { + if (vgi->vgi_guard != vga->vga_guard) { + error = EPERM; + } else { + vga->vga_attrs = vgi->vgi_attrs; + } + } + } + + lck_rw_unlock_shared(&llock); + vnode_put(vp); + } while (0); + + fp_drop(p, fd, fp, 0); + return error; +} + +static int +vnguard_sysc_setguard(proc_t p, const struct vnguard_set *vns) +{ + const int fd = vns->vns_fd; + + if ((vns->vns_attrs & ~VNG_ALL) != 0 || + 0 == vns->vns_attrs || 0 == vns->vns_guard) { + return EINVAL; + } + + int error; + struct fileproc *fp; + if (0 != (error = fp_lookup(p, fd, &fp, 0))) { + return error; + } + do { + /* + * To avoid trivial DoS, insist that the caller + * has read/write access to the file. + */ + if ((FREAD | FWRITE) != (fp->f_flag & (FREAD | FWRITE))) { + error = EBADF; + break; + } + struct fileglob *fg = fp->f_fglob; + if (FILEGLOB_DTYPE(fg) != DTYPE_VNODE) { + error = EBADF; + break; + } + /* + * Confinement means there's only one fd pointing at + * this fileglob, and will always be associated with + * this pid. + */ + if (0 == (FG_CONFINED & fg->fg_lflags)) { + error = EBADF; + break; + } + struct vnode *vp = fg->fg_data; + if (!vnode_isreg(vp) || NULL == vp->v_mount) { + error = EBADF; + break; + } + error = vnode_getwithref(vp); + if (0 != error) { + break; + } + + /* Ensure the target vnode -has- a label */ + struct vfs_context *ctx = vfs_context_current(); + mac_vnode_label_update(ctx, vp, NULL); + + struct vng_info *nvgi = new_vgi(vns->vns_attrs, vns->vns_guard); + struct vng_owner *nvgo = new_vgo(p, fg); + + lck_rw_lock_exclusive(&llock); + + do { + /* + * A vnode guard is associated with one or more + * fileglobs in one or more processes. + */ + struct vng_info *vgi = vng_lbl_get(vp->v_label); + struct vng_owner *vgo = vng_lbl_get(fg->fg_label); + + if (NULL == vgi) { + /* vnode unguarded, add the first guard */ + if (NULL != vgo) { + panic("vnguard label on fileglob " + "but not vnode"); + } + /* add a kusecount so we can unlabel later */ + error = vnode_ref_ext(vp, O_EVTONLY, 0); + if (0 == error) { + /* add the guard */ + vgi_add_vgo(nvgi, nvgo); + vng_lbl_set(vp->v_label, nvgi); + vng_lbl_set(fg->fg_label, nvgo); + } else { + free_vgo(nvgo); + free_vgi(nvgi); + } + } else { + /* vnode already guarded */ + free_vgi(nvgi); + if (vgi->vgi_guard != vns->vns_guard) { + error = EPERM; /* guard mismatch */ + } else if (vgi->vgi_attrs != vns->vns_attrs) { + /* + * Temporary workaround for older versions of SQLite: + * allow newer guard attributes to be silently cleared. + */ + const unsigned mask = ~(VNG_WRITE_OTHER | VNG_TRUNC_OTHER); + if ((vgi->vgi_attrs & mask) == (vns->vns_attrs & mask)) { + vgi->vgi_attrs &= vns->vns_attrs; + } else { + error = EACCES; /* attr mismatch */ + } + } + if (0 != error || NULL != vgo) { + free_vgo(nvgo); + break; + } + /* record shared ownership */ + vgi_add_vgo(vgi, nvgo); + vng_lbl_set(fg->fg_label, nvgo); + } + } while (0); + + lck_rw_unlock_exclusive(&llock); + vnode_put(vp); + } while (0); + + fp_drop(p, fd, fp, 0); + return error; +} + +static int +vng_policy_syscall(proc_t p, int cmd, user_addr_t arg) +{ + int error = EINVAL; + + switch (cmd) { + case VNG_SYSC_PING: + if (0 == arg) { + error = 0; + } + break; + case VNG_SYSC_SET_GUARD: { + struct vnguard_set vns; + error = copyin(arg, (void *)&vns, sizeof(vns)); + if (error) { + break; + } + error = vnguard_sysc_setguard(p, &vns); + break; + } + case VNG_SYSC_GET_ATTR: { + struct vnguard_getattr vga; + error = copyin(arg, (void *)&vga, sizeof(vga)); + if (error) { + break; + } + error = vnguard_sysc_getguardattr(p, &vga); + if (error) { + break; + } + error = copyout((void *)&vga, arg, sizeof(vga)); + break; + } + default: + break; + } + return error; +} + +/* + * This is called just before the fileglob disappears in fg_free(). + * Take the exclusive lock: no other thread can add or remove + * a vng_info to any vnode in the system. + */ +static void +vng_file_label_destroy(struct label *label) +{ + lck_rw_lock_exclusive(&llock); + struct vng_owner *lvgo = vng_lbl_get(label); + if (lvgo) { + vng_lbl_set(label, 0); + struct vng_info *vgi = lvgo->vgo_vgi; + assert(vgi); + if (vgi_remove_vgo(vgi, lvgo)) { + /* that was the last reference */ + vgi->vgi_attrs = 0; + struct fileglob *fg = lvgo->vgo_fg; + assert(fg); + if (DTYPE_VNODE == FILEGLOB_DTYPE(fg)) { + struct vnode *vp = fg->fg_data; + int error = vnode_getwithref(vp); + if (0 == error) { + vng_lbl_set(vp->v_label, 0); + lck_rw_unlock_exclusive(&llock); + /* may trigger VNOP_INACTIVE */ + vnode_rele_ext(vp, O_EVTONLY, 0); + vnode_put(vp); + free_vgi(vgi); + free_vgo(lvgo); + return; + } + } + } + free_vgo(lvgo); + } + lck_rw_unlock_exclusive(&llock); +} + +static os_reason_t +vng_reason_from_pathname(const char *path, uint32_t pathlen) +{ + os_reason_t r = os_reason_create(OS_REASON_GUARD, GUARD_REASON_VNODE); + if (NULL == r) { + return r; + } + /* + * If the pathname is very long, just keep the trailing part + */ + const uint32_t pathmax = 3 * EXIT_REASON_USER_DESC_MAX_LEN / 4; + if (pathlen > pathmax) { + path += (pathlen - pathmax); + pathlen = pathmax; + } + uint32_t rsize = kcdata_estimate_required_buffer_size(1, pathlen); + if (0 == os_reason_alloc_buffer(r, rsize)) { + struct kcdata_descriptor *kcd = &r->osr_kcd_descriptor; + mach_vm_address_t addr; + if (kcdata_get_memory_addr(kcd, + EXIT_REASON_USER_DESC, pathlen, &addr) == KERN_SUCCESS) { + kcdata_memcpy(kcd, addr, path, pathlen); + return r; + } + } + os_reason_free(r); + return OS_REASON_NULL; +} + +static int vng_policy_flags; + +/* + * Note: if an EXC_GUARD is generated, llock will be dropped and + * subsequently reacquired by this routine. Data derived from + * any label in the caller should be regenerated. + */ +static int +vng_guard_violation(const struct vng_info *vgi, + unsigned opval, vnode_t vp) +{ + int retval = 0; + + if (vng_policy_flags & kVNG_POLICY_EPERM) { + /* deny the operation */ + retval = EPERM; + } + + if (vng_policy_flags & (kVNG_POLICY_LOGMSG | kVNG_POLICY_UPRINTMSG)) { + /* log a message */ + const char *op; + switch (opval) { + case VNG_RENAME_FROM: + op = "rename-from"; + break; + case VNG_RENAME_TO: + op = "rename-to"; + break; + case VNG_UNLINK: + op = "unlink"; + break; + case VNG_LINK: + op = "link"; + break; + case VNG_EXCHDATA: + op = "exchdata"; + break; + case VNG_WRITE_OTHER: + op = "write"; + break; + case VNG_TRUNC_OTHER: + op = "truncate"; + break; + default: + op = "(unknown)"; + break; + } + + const char *nm = vnode_getname(vp); + proc_t p = current_proc(); + const struct vng_owner *vgo; + TAILQ_FOREACH(vgo, &vgi->vgi_owners, vgo_link) { + const char fmt[] = + "%s[%d]: %s%s: '%s' guarded by %s[%d] (0x%llx)\n"; + + if (vng_policy_flags & kVNG_POLICY_LOGMSG) { + printf(fmt, + proc_name_address(p), proc_pid(p), op, + 0 != retval ? " denied" : "", + NULL != nm ? nm : "(unknown)", + proc_name_address(vgo->vgo_p), + proc_pid(vgo->vgo_p), vgi->vgi_guard); + } + if (vng_policy_flags & kVNG_POLICY_UPRINTMSG) { + uprintf(fmt, + proc_name_address(p), proc_pid(p), op, + 0 != retval ? " denied" : "", + NULL != nm ? nm : "(unknown)", + proc_name_address(vgo->vgo_p), + proc_pid(vgo->vgo_p), vgi->vgi_guard); + } + } + if (NULL != nm) { + vnode_putname(nm); + } + } + + if (vng_policy_flags & (kVNG_POLICY_EXC | kVNG_POLICY_EXC_CORPSE)) { + /* EXC_GUARD exception */ + const struct vng_owner *vgo = TAILQ_FIRST(&vgi->vgi_owners); + pid_t pid = vgo ? proc_pid(vgo->vgo_p) : 0; + mach_exception_code_t code; + mach_exception_subcode_t subcode; + + code = 0; + EXC_GUARD_ENCODE_TYPE(code, GUARD_TYPE_VN); + EXC_GUARD_ENCODE_FLAVOR(code, opval); + EXC_GUARD_ENCODE_TARGET(code, pid); + subcode = vgi->vgi_guard; + + lck_rw_unlock_shared(&llock); + + if (vng_policy_flags & kVNG_POLICY_EXC_CORPSE) { + char *path; + int len = MAXPATHLEN; + MALLOC(path, char *, len, M_TEMP, M_WAITOK); + os_reason_t r = NULL; + if (NULL != path) { + vn_getpath(vp, path, &len); + if (*path && len) { + r = vng_reason_from_pathname(path, len); + } + } + task_violated_guard(code, subcode, r); /* not fatal */ + if (NULL != r) { + os_reason_free(r); + } + if (NULL != path) { + FREE(path, M_TEMP); + } + } else { + thread_t t = current_thread(); + thread_guard_violation(t, code, subcode, TRUE); + } + + lck_rw_lock_shared(&llock); + } else if (vng_policy_flags & kVNG_POLICY_SIGKILL) { + proc_t p = current_proc(); + psignal(p, SIGKILL); + } + + return retval; +} + +/* + * A fatal vnode guard was tripped on this thread. + * + * (Invoked before returning to userland from the syscall handler.) + */ +void +vn_guard_ast(thread_t __unused t, + mach_exception_data_type_t code, mach_exception_data_type_t subcode) +{ + task_exception_notify(EXC_GUARD, code, subcode); + proc_t p = current_proc(); + psignal(p, SIGKILL); +} + +/* + * vnode callbacks + */ + +static int +vng_vnode_check_rename(kauth_cred_t __unused cred, + struct vnode *__unused dvp, struct label *__unused dlabel, + struct vnode *vp, struct label *label, + struct componentname *__unused cnp, + struct vnode *__unused tdvp, struct label *__unused tdlabel, + struct vnode *tvp, struct label *tlabel, + struct componentname *__unused tcnp) +{ + int error = 0; + if (NULL != label || NULL != tlabel) { + lck_rw_lock_shared(&llock); + const struct vng_info *vgi = + vng_lbl_get_withattr(label, VNG_RENAME_FROM); + if (NULL != vgi) { + error = vng_guard_violation(vgi, VNG_RENAME_FROM, vp); + } + if (0 == error) { + vgi = vng_lbl_get_withattr(tlabel, VNG_RENAME_TO); + if (NULL != vgi) { + error = vng_guard_violation(vgi, + VNG_RENAME_TO, tvp); + } + } + lck_rw_unlock_shared(&llock); + } + return error; +} + +static int +vng_vnode_check_link(kauth_cred_t __unused cred, + struct vnode *__unused dvp, struct label *__unused dlabel, + struct vnode *vp, struct label *label, struct componentname *__unused cnp) +{ + int error = 0; + if (NULL != label) { + lck_rw_lock_shared(&llock); + const struct vng_info *vgi = + vng_lbl_get_withattr(label, VNG_LINK); + if (vgi) { + error = vng_guard_violation(vgi, VNG_LINK, vp); + } + lck_rw_unlock_shared(&llock); + } + return error; +} + +static int +vng_vnode_check_unlink(kauth_cred_t __unused cred, + struct vnode *__unused dvp, struct label *__unused dlabel, + struct vnode *vp, struct label *label, struct componentname *__unused cnp) +{ + int error = 0; + if (NULL != label) { + lck_rw_lock_shared(&llock); + const struct vng_info *vgi = + vng_lbl_get_withattr(label, VNG_UNLINK); + if (vgi) { + error = vng_guard_violation(vgi, VNG_UNLINK, vp); + } + lck_rw_unlock_shared(&llock); + } + return error; } - + +/* + * Only check violations for writes performed by "other processes" + */ +static int +vng_vnode_check_write(kauth_cred_t __unused actv_cred, + kauth_cred_t __unused file_cred, struct vnode *vp, struct label *label) +{ + int error = 0; + if (NULL != label) { + lck_rw_lock_shared(&llock); + const struct vng_info *vgi = + vng_lbl_get_withattr(label, VNG_WRITE_OTHER); + if (vgi) { + proc_t p = current_proc(); + const struct vng_owner *vgo; + TAILQ_FOREACH(vgo, &vgi->vgi_owners, vgo_link) { + if (vgo->vgo_p == p) { + goto done; + } + } + error = vng_guard_violation(vgi, VNG_WRITE_OTHER, vp); + } +done: + lck_rw_unlock_shared(&llock); + } + return error; +} + +/* + * Only check violations for truncates performed by "other processes" + */ +static int +vng_vnode_check_truncate(kauth_cred_t __unused actv_cred, + kauth_cred_t __unused file_cred, struct vnode *vp, + struct label *label) +{ + int error = 0; + if (NULL != label) { + lck_rw_lock_shared(&llock); + const struct vng_info *vgi = + vng_lbl_get_withattr(label, VNG_TRUNC_OTHER); + if (vgi) { + proc_t p = current_proc(); + const struct vng_owner *vgo; + TAILQ_FOREACH(vgo, &vgi->vgi_owners, vgo_link) { + if (vgo->vgo_p == p) { + goto done; + } + } + error = vng_guard_violation(vgi, VNG_TRUNC_OTHER, vp); + } +done: + lck_rw_unlock_shared(&llock); + } + return error; +} + +static int +vng_vnode_check_exchangedata(kauth_cred_t __unused cred, + struct vnode *fvp, struct label *flabel, + struct vnode *svp, struct label *slabel) +{ + int error = 0; + if (NULL != flabel || NULL != slabel) { + lck_rw_lock_shared(&llock); + const struct vng_info *vgi = + vng_lbl_get_withattr(flabel, VNG_EXCHDATA); + if (NULL != vgi) { + error = vng_guard_violation(vgi, VNG_EXCHDATA, fvp); + } + if (0 == error) { + vgi = vng_lbl_get_withattr(slabel, VNG_EXCHDATA); + if (NULL != vgi) { + error = vng_guard_violation(vgi, + VNG_EXCHDATA, svp); + } + } + lck_rw_unlock_shared(&llock); + } + return error; +} + +/* Intercept open-time truncations (by "other") of a guarded vnode */ + +static int +vng_vnode_check_open(kauth_cred_t cred, + struct vnode *vp, struct label *label, int acc_mode) +{ + if (0 == (acc_mode & O_TRUNC)) { + return 0; + } + return vng_vnode_check_truncate(cred, NULL, vp, label); +} + +/* + * Configuration gorp + */ + +static void +vng_init(struct mac_policy_conf *mpc) +{ + llock_grp = lck_grp_alloc_init(mpc->mpc_name, LCK_GRP_ATTR_NULL); + lck_rw_init(&llock, llock_grp, LCK_ATTR_NULL); +} + +SECURITY_READ_ONLY_EARLY(static struct mac_policy_ops) vng_policy_ops = { + .mpo_file_label_destroy = vng_file_label_destroy, + + .mpo_vnode_check_link = vng_vnode_check_link, + .mpo_vnode_check_unlink = vng_vnode_check_unlink, + .mpo_vnode_check_rename = vng_vnode_check_rename, + .mpo_vnode_check_write = vng_vnode_check_write, + .mpo_vnode_check_truncate = vng_vnode_check_truncate, + .mpo_vnode_check_exchangedata = vng_vnode_check_exchangedata, + .mpo_vnode_check_open = vng_vnode_check_open, + + .mpo_policy_syscall = vng_policy_syscall, + .mpo_policy_init = vng_init, +}; + +static const char *vng_labelnames[] = { + "vnguard", +}; + +#define ACOUNT(arr) ((unsigned)(sizeof (arr) / sizeof (arr[0]))) + +SECURITY_READ_ONLY_LATE(static struct mac_policy_conf) vng_policy_conf = { + .mpc_name = VNG_POLICY_NAME, + .mpc_fullname = "Guarded vnode policy", + .mpc_field_off = &label_slot, + .mpc_labelnames = vng_labelnames, + .mpc_labelname_count = ACOUNT(vng_labelnames), + .mpc_ops = &vng_policy_ops, + .mpc_loadtime_flags = 0, + .mpc_runtime_flags = 0 +}; + +SECURITY_READ_ONLY_LATE(static mac_policy_handle_t) vng_policy_handle; + +void +vnguard_policy_init(void) +{ + if (0 == PE_i_can_has_debugger(NULL)) { + return; + } + vng_policy_flags = kVNG_POLICY_LOGMSG | + kVNG_POLICY_EXC_CORPSE | kVNG_POLICY_UPRINTMSG; + PE_parse_boot_argn("vnguard", &vng_policy_flags, sizeof(vng_policy_flags)); + if (vng_policy_flags) { + mac_policy_register(&vng_policy_conf, &vng_policy_handle, NULL); + } +} + +#if DEBUG || DEVELOPMENT +#include + +SYSCTL_DECL(_kern_vnguard); +SYSCTL_NODE(_kern, OID_AUTO, vnguard, CTLFLAG_RW | CTLFLAG_LOCKED, 0, "vnguard"); +SYSCTL_INT(_kern_vnguard, OID_AUTO, flags, CTLFLAG_RW | CTLFLAG_LOCKED, + &vng_policy_flags, 0, "vnguard policy flags"); +#endif + +#endif /* CONFIG_MACF && CONFIG_VNGUARD */