#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 <sys/vnode_internal.h>
#include <sys/uio_internal.h>
#include <sys/ubc_internal.h>
#include <vfs/vfs_support.h>
#include <security/audit/audit.h>
#include <sys/syscall.h>
#include <sys/kauth.h>
#include <sys/kdebug.h>
#include <stdbool.h>
#include <vm/vm_protos.h>
#if CONFIG_PROTECT
#include <sys/cprotect.h>
#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);
kern_return_t task_exception_notify(exception_type_t exception,
mach_exception_data_type_t code, mach_exception_data_type_t subcode);
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, int locked)
{
struct fileproc *fp;
int error;
if ((error = fp_lookup(p, fd, &fp, locked)) != 0)
return (error);
if (FILEPROC_TYPE(fp) != FTYPE_GUARDED) {
(void) fp_drop(p, fd, fp, locked);
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, locked);
return (EPERM);
}
if (gfpp)
*gfpp = gfp;
return (0);
}
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) == attrs);
}
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);
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;
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 {
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);
}
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;
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);
}
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 | GUARD_WRITE))
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);
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_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);
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);
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)) {
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));
}
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(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, 1)) != 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(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);
if (0 != uap->nguard &&
0 != (error = copyin(uap->nguard, &newg, sizeof (newg))))
return (error);
if (0 != uap->fdflagsp &&
0 != (error = copyin(uap->fdflagsp, &nfdflags, sizeof (nfdflags))))
return (error);
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;
}
proc_fdlock(p);
}
if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) {
if (0 == uap->guard || 0 == uap->guardflags)
error = EINVAL;
else if (0 == oldg)
error = EPERM;
} else {
if (0 != uap->guard || 0 != uap->guardflags)
error = EINVAL;
}
if (0 != error)
goto dropout;
if (0 != uap->nguard) {
if (0 == newg)
error = EINVAL;
else if (((uap->nguardflags & GUARD_REQUIRED) != GUARD_REQUIRED) ||
((uap->nguardflags & ~GUARD_ALL) != 0))
error = EINVAL;
if (0 != error)
goto dropout;
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);
if (oldg == gfp->gf_guard &&
uap->guardflags == gfp->gf_attrs) {
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 {
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);
struct guarded_fileproc *gfp;
proc_fdlock(p);
switch (error = fp_tryswap(p, fd, nfp)) {
case 0:
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:
(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 {
if (FILEPROC_TYPE(fp) == FTYPE_GUARDED) {
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:
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:
(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 {
error = EINVAL;
}
}
dropout:
(void) fp_drop(p, fd, fp, 1);
proc_fdunlock(p);
return (error);
}
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);
}
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);
}
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);
if (uap->iovcnt <= 0 || uap->iovcnt > UIO_MAXIOV)
return (EINVAL);
auio = uio_create(uap->iovcnt, 0,
(IS_64BIT_PROCESS(p) ? UIO_USERSPACE64 : UIO_USERSPACE32),
UIO_WRITE);
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;
}
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);
}