diff --git a/head/sys/kern/uipc_usrreq.c b/head/sys/kern/uipc_usrreq.c --- a/head/sys/kern/uipc_usrreq.c +++ b/head/sys/kern/uipc_usrreq.c @@ -160,6 +160,7 @@ static u_long unpdg_recvspace = 16*1024; /* support 8KB syslog msgs */ static u_long unpsp_sendspace = PIPSIZ; /* really max datagram size */ static u_long unpsp_recvspace = PIPSIZ; +static int sendfd_check_root = 1; static SYSCTL_NODE(_net, PF_LOCAL, local, CTLFLAG_RW | CTLFLAG_MPSAFE, 0, "Local domain"); @@ -185,6 +186,9 @@ &unpsp_sendspace, 0, "Default seqpacket send space."); SYSCTL_ULONG(_net_local_seqpacket, OID_AUTO, recvspace, CTLFLAG_RW, &unpsp_recvspace, 0, "Default seqpacket receive space."); +SYSCTL_INT(_net_local, OID_AUTO, sendfd_check_root, CTLFLAG_RW, + &sendfd_check_root, 0, + "recvmsg() will deny SCM_RIGHTS with outside-root directories."); SYSCTL_INT(_net_local, OID_AUTO, inflight, CTLFLAG_RD, &unp_rights, 0, "File descriptors in flight."); SYSCTL_INT(_net_local, OID_AUTO, deferred, CTLFLAG_RD, @@ -2006,6 +2010,36 @@ } static int +verify_dir_fds(struct filedescent **fdep, int n, int strict) +{ + struct pwd *pwd; + struct vnode *dp; + struct file *fp; + int i, error; + + if (n == 0) + return (0); + pwd = pwd_hold(curthread); + error = 0; + for (i=0; ifde_file; + if (fp == NULL || fp->f_ops == &badfileops) { + error = EBADF; + break; + } + if ((dp = fp->f_vnode) == NULL || (dp->v_type != VDIR)) + continue; + vrefact(dp); + error = vn_dir_check_root(dp, pwd->pwd_rdir, pwd->pwd_jdir, + strict); + if (error != 0) + break; + } + pwd_drop(pwd); + return (error); +} + +static int unp_externalize(struct mbuf *control, struct mbuf **controlp, int flags) { struct thread *td = curthread; /* XXX */ @@ -2043,6 +2077,16 @@ unp_freerights(fdep, newfds); goto next; } + + /* Check chrooted access to directory fds */ + if (sendfd_check_root && + verify_dir_fds(fdep, newfds, + (sendfd_check_root > 1)) != 0) { + error = EPERM; + unp_freerights(fdep, newfds); + goto next; + } + FILEDESC_XLOCK(fdesc); /* diff --git a/head/sys/kern/vfs_lookup.c b/head/sys/kern/vfs_lookup.c --- a/head/sys/kern/vfs_lookup.c +++ b/head/sys/kern/vfs_lookup.c @@ -1513,6 +1513,109 @@ } /* + * Check if a vnode is under root-vnode, to prevent various chroot-breaks + * Input: referenced unlocked 'dp', will unref it + * return: 0 = ok, vnode is under current root + * EJUSTRETURN = vnode is outside current root + * other values = got a error during check + */ +int +vn_dir_check_root(struct vnode *dp, struct vnode *rootdp, struct vnode *topdp, + int strict) +{ + struct vnode *pardp; + struct prison *pr; + int error; + struct componentname cn; + + cn.cn_nameiop = LOOKUP; + cn.cn_cred = curthread->td_ucred; + vn_lock(dp, + compute_cn_lkflags(dp->v_mount, LK_SHARED | LK_RETRY, 0)); + for (;;) { + if (VN_IS_DOOMED(dp)) { + vput(dp); + return (ENOENT); + } + MPASS(dp->v_type == VDIR); + /* XXX not sure if NULL valid here for VOP_LOOKUP */ + cn.cn_pnbuf = NULL; + cn.cn_nameptr = "../../../"; + cn.cn_namelen = 2; + cn.cn_flags = MAKEENTRY | ISDOTDOT; + if (strict == 0) + cn.cn_flags |= NOEXECCHECK; +#ifdef INVARIANTS + cn.cn_origflags = cn.cn_flags; +#endif + + for (;;) { + /* check if we are at requested root */ + if (dp == rootdp) { + vput(dp); + return (0); + } + /* check for local (chrooted/jailed) or global root */ + for (pr = cn.cn_cred->cr_prison; pr != NULL; + pr = pr->pr_parent) + if (dp == pr->pr_root) + break; + if (dp == topdp || dp == rootvnode || pr != NULL) { + printf("caught out-of-chroot vnode, pid %d\n", + (int)curthread->td_proc->p_pid); + vput(dp); + return (EJUSTRETURN); + } + /* + * Check if we are at mounted filesystem root. + * If so, replace the vnode with underlying mountpoint + * and do root-node checks again before traversing ../ + */ + if ((dp->v_vflag & VV_ROOT) == 0) + break; + if (VN_IS_DOOMED(dp) || + (pardp = dp->v_mount->mnt_vnodecovered) == NULL || + pardp->v_mountedhere != dp->v_mount) { + /* forced unmount */ + vput(dp); + return (ENOENT); + } + VREF(pardp); + vput(dp); + dp = pardp; + vn_lock(dp, compute_cn_lkflags(dp->v_mount, + LK_SHARED | LK_RETRY, ISDOTDOT)); + } + + ASSERT_VOP_LOCKED(dp, "lookup_find_root"); + if (VN_IS_DOOMED(dp)) { + vput(dp); + return (ENOENT); + } + cn.cn_lkflags = + compute_cn_lkflags(dp->v_mount, LK_SHARED, cn.cn_flags); + pardp = NULL; + if ((error = VOP_LOOKUP(dp, &pardp, &cn)) != 0) { + KASSERT(pardp == NULL, ("leaf should be empty")); + if (error == ERELOOKUP) + continue; + MPASS(error != EJUSTRETURN); + vput(dp); + return (error); + } + + MPASS(pardp != NULL); + if (dp == pardp) { + vrele(dp); + vput(pardp); + return (ENOENT); + } + vput(dp); + dp = pardp; + } +} + +/* * Free data allocated by namei(); see namei(9) for details. */ void diff --git a/head/sys/sys/vnode.h b/head/sys/sys/vnode.h --- a/head/sys/sys/vnode.h +++ b/head/sys/sys/vnode.h @@ -1112,6 +1112,8 @@ void vn_fsid(struct vnode *vp, struct vattr *va); int vn_dir_check_exec(struct vnode *vp, struct componentname *cnp); +int vn_dir_check_root(struct vnode *dp, struct vnode *rootdp, + struct vnode *topdp, int strict); int vn_lktype_write(struct mount *mp, struct vnode *vp); #define VOP_UNLOCK_FLAGS(vp, flags) ({ \