Index: sys/fs/nfsserver/nfs_nfsdport.c =================================================================== --- sys/fs/nfsserver/nfs_nfsdport.c +++ sys/fs/nfsserver/nfs_nfsdport.c @@ -349,7 +349,7 @@ *retdirp = NULL; cnp->cn_nameptr = cnp->cn_pnbuf; - ndp->ni_strictrelative = 0; + ndp->ni_nonrelativeerrno = 0; /* * Extract and set starting directory. */ Index: sys/kern/vfs_lookup.c =================================================================== --- sys/kern/vfs_lookup.c +++ sys/kern/vfs_lookup.c @@ -182,7 +182,7 @@ */ if (error == 0 && IN_CAPABILITY_MODE(td) && (cnp->cn_flags & NOCAPCHECK) == 0) { - ndp->ni_strictrelative = 1; + ndrequire_strict_relative_lookups(ndp, ENOTCAPABLE); if (ndp->ni_dirfd == AT_FDCWD) { #ifdef KTRACE if (KTRPOINT(td, KTR_CAPFAIL)) @@ -248,7 +248,7 @@ &rights) || ndp->ni_filecaps.fc_fcntls != CAP_FCNTL_ALL || ndp->ni_filecaps.fc_nioctls != -1) { - ndp->ni_strictrelative = 1; + ndrequire_strict_relative_lookups(ndp, ENOTCAPABLE); } #endif } @@ -281,13 +281,13 @@ cnp->cn_nameptr = cnp->cn_pnbuf; if (*(cnp->cn_nameptr) == '/') { vrele(dp); - if (ndp->ni_strictrelative != 0) { + if (ndp->ni_nonrelativeerrno != 0) { #ifdef KTRACE if (KTRPOINT(curthread, KTR_CAPFAIL)) ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); #endif namei_cleanup_cnp(cnp); - return (ENOTCAPABLE); + return (ndp->ni_nonrelativeerrno); } while (*(cnp->cn_nameptr) == '/') { cnp->cn_nameptr++; @@ -622,12 +622,12 @@ * the jail or chroot, don't let them out. */ if (cnp->cn_flags & ISDOTDOT) { - if (ndp->ni_strictrelative != 0) { + if (ndp->ni_nonrelativeerrno != 0) { #ifdef KTRACE if (KTRPOINT(curthread, KTR_CAPFAIL)) ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); #endif - error = ENOTCAPABLE; + error = ndp->ni_nonrelativeerrno; goto bad; } if ((cnp->cn_flags & ISLASTCN) != 0 && @@ -1062,7 +1062,7 @@ ndp->ni_dirp = namep; ndp->ni_dirfd = dirfd; ndp->ni_startdir = startdir; - ndp->ni_strictrelative = 0; + ndp->ni_nonrelativeerrno = 0; if (rightsp != NULL) ndp->ni_rightsneeded = *rightsp; else @@ -1123,6 +1123,38 @@ } } +void +ndrequire_strict_relative_lookups(struct nameidata *ndp, int errnum) +{ + /* + * Monotonic ordering of possible field values: + * + * EPERM - user-level code has requested an O_BENEATH lookup + * ENOTCAPABLE - Capsicum demands strict relative lookups + * 0 - allow relative lookups + * + * We can always move up this list but never down. + */ + + KASSERT((errnum == EPERM) || (errnum == ENOTCAPABLE), + ("invalid errno (not EPERM or ENOTCAPABLE)")); + + switch (errnum) { + case EPERM: + ndp->ni_nonrelativeerrno = errnum; + break; + + case ENOTCAPABLE: + if (ndp->ni_nonrelativeerrno != EPERM) { + ndp->ni_nonrelativeerrno = errnum; + } + break; + + default: + KASSERT(false, ("invalid errno for strict relative lookups")); + } +} + /* * Determine if there is a suitable alternate filename under the specified * prefix for the specified path. If the create flag is set, then the Index: sys/kern/vfs_syscalls.c =================================================================== --- sys/kern/vfs_syscalls.c +++ sys/kern/vfs_syscalls.c @@ -1049,7 +1049,7 @@ struct vnode *vp; struct nameidata nd; cap_rights_t rights; - int cmode, error, indx; + int beneath, cmode, error, indx; indx = -1; @@ -1058,6 +1058,7 @@ /* XXX: audit dirfd */ cap_rights_init(&rights, CAP_LOOKUP); flags_to_rights(flags, &rights); + beneath = ((flags & O_BENEATH) == O_BENEATH); /* * Only one of the O_EXEC, O_RDONLY, O_WRONLY and O_RDWR flags * may be specified. @@ -1086,6 +1087,9 @@ cmode = ((mode & ~fdp->fd_cmask) & ALLPERMS) & ~S_ISTXT; NDINIT_ATRIGHTS(&nd, LOOKUP, FOLLOW | AUDITVNODE1, pathseg, path, fd, &rights, td); + if (beneath) { + ndrequire_strict_relative_lookups(&nd, EPERM); + } td->td_dupfd = -1; /* XXX check for fdopen */ error = vn_open(&nd, &flags, cmode, fp); if (error != 0) { @@ -1100,11 +1104,11 @@ /* * Handle special fdopen() case. bleh. * - * Don't do this for relative (capability) lookups; we don't + * Don't do this for capability or O_BENEATH lookups: we don't * understand exactly what would happen, and we don't think * that it ever should. */ - if (nd.ni_strictrelative == 0 && + if (nd.ni_nonrelativeerrno == 0 && (error == ENODEV || error == ENXIO) && td->td_dupfd >= 0) { error = dupfdopen(td, fdp, td->td_dupfd, flags, error, @@ -1150,8 +1154,11 @@ struct filecaps *fcaps; #ifdef CAPABILITIES - if (nd.ni_strictrelative == 1) + if (nd.ni_nonrelativeerrno != 0 && + cap_rights_is_valid(&nd.ni_filecaps.fc_rights)) + { fcaps = &nd.ni_filecaps; + } else #endif fcaps = NULL; Index: sys/sys/fcntl.h =================================================================== --- sys/sys/fcntl.h +++ sys/sys/fcntl.h @@ -131,6 +131,7 @@ #if __BSD_VISIBLE #define O_VERIFY 0x00200000 /* open only after verification */ +#define O_BENEATH 0x00400000 /* stay beneath a specified directory */ #endif /* Index: sys/sys/namei.h =================================================================== --- sys/sys/namei.h +++ sys/sys/namei.h @@ -73,7 +73,7 @@ struct vnode *ni_rootdir; /* logical root directory */ struct vnode *ni_topdir; /* logical top directory */ int ni_dirfd; /* starting directory for *at functions */ - int ni_strictrelative; /* relative lookup only; no '..' */ + int ni_nonrelativeerrno; /* errno for non-relative (..) lookup */ /* * Results: returned from namei */ @@ -150,7 +150,8 @@ #define AUDITVNODE2 0x08000000 /* audit the looked up vnode information */ #define TRAILINGSLASH 0x10000000 /* path ended in a slash */ #define NOCAPCHECK 0x20000000 /* do not perform capability checks */ -#define PARAMASK 0x3ffffe00 /* mask of parameter descriptors */ +#define BENEATH 0x40000000 /* strict downwards-only lookup */ +#define PARAMASK 0x7ffffe00 /* mask of parameter descriptors */ /* * Initialization of a nameidata structure. @@ -168,6 +169,12 @@ enum uio_seg segflg, const char *namep, int dirfd, struct vnode *startdir, cap_rights_t *rightsp, struct thread *td); +/* + * Set the ni_nonrelativeerrno field of a struct nameidata according to a + * monotonically-increasing policy. + */ +void ndrequire_strict_relative_lookups(struct nameidata*, int errnum); + #define NDF_NO_DVP_RELE 0x00000001 #define NDF_NO_DVP_UNLOCK 0x00000002 #define NDF_NO_DVP_PUT 0x00000003