Changeset View
Changeset View
Standalone View
Standalone View
sys/kern/vfs_lookup.c
Show First 20 Lines • Show All 176 Lines • ▼ Show 20 Lines | |||||
nameicap_tracker_add(struct nameidata *ndp, struct vnode *dp) | nameicap_tracker_add(struct nameidata *ndp, struct vnode *dp) | ||||
{ | { | ||||
struct nameicap_tracker *nt; | struct nameicap_tracker *nt; | ||||
struct componentname *cnp; | struct componentname *cnp; | ||||
if ((ndp->ni_lcf & NI_LCF_CAP_DOTDOT) == 0 || dp->v_type != VDIR) | if ((ndp->ni_lcf & NI_LCF_CAP_DOTDOT) == 0 || dp->v_type != VDIR) | ||||
return; | return; | ||||
cnp = &ndp->ni_cnd; | cnp = &ndp->ni_cnd; | ||||
if ((cnp->cn_flags & BENEATH) != 0 && | nt = TAILQ_LAST(&ndp->ni_cap_tracker, nameicap_tracker_head); | ||||
(ndp->ni_lcf & NI_LCF_BENEATH_LATCHED) == 0) { | if (nt != NULL && nt->dp == dp) | ||||
MPASS((ndp->ni_lcf & NI_LCF_LATCH) != 0); | |||||
if (dp != ndp->ni_beneath_latch) | |||||
return; | return; | ||||
ndp->ni_lcf |= NI_LCF_BENEATH_LATCHED; | |||||
} | |||||
nt = malloc(sizeof(*nt), M_NAMEITRACKER, M_WAITOK); | nt = malloc(sizeof(*nt), M_NAMEITRACKER, M_WAITOK); | ||||
vhold(dp); | vhold(dp); | ||||
nt->dp = dp; | nt->dp = dp; | ||||
TAILQ_INSERT_TAIL(&ndp->ni_cap_tracker, nt, nm_link); | TAILQ_INSERT_TAIL(&ndp->ni_cap_tracker, nt, nm_link); | ||||
} | } | ||||
static void | static void | ||||
nameicap_cleanup(struct nameidata *ndp, bool clean_latch) | nameicap_cleanup_from(struct nameidata *ndp, struct nameicap_tracker *first) | ||||
{ | { | ||||
struct nameicap_tracker *nt, *nt1; | struct nameicap_tracker *nt, *nt1; | ||||
KASSERT(TAILQ_EMPTY(&ndp->ni_cap_tracker) || | nt = first; | ||||
(ndp->ni_lcf & NI_LCF_CAP_DOTDOT) != 0, ("not strictrelative")); | TAILQ_FOREACH_FROM_SAFE(nt, &ndp->ni_cap_tracker, nm_link, nt1) { | ||||
TAILQ_FOREACH_SAFE(nt, &ndp->ni_cap_tracker, nm_link, nt1) { | |||||
TAILQ_REMOVE(&ndp->ni_cap_tracker, nt, nm_link); | TAILQ_REMOVE(&ndp->ni_cap_tracker, nt, nm_link); | ||||
vdrop(nt->dp); | vdrop(nt->dp); | ||||
free(nt, M_NAMEITRACKER); | free(nt, M_NAMEITRACKER); | ||||
} | } | ||||
if (clean_latch && (ndp->ni_lcf & NI_LCF_LATCH) != 0) { | |||||
ndp->ni_lcf &= ~NI_LCF_LATCH; | |||||
vrele(ndp->ni_beneath_latch); | |||||
} | } | ||||
static void | |||||
nameicap_cleanup(struct nameidata *ndp) | |||||
{ | |||||
KASSERT(TAILQ_EMPTY(&ndp->ni_cap_tracker) || | |||||
(ndp->ni_lcf & NI_LCF_CAP_DOTDOT) != 0, ("not strictrelative")); | |||||
nameicap_cleanup_from(ndp, NULL); | |||||
} | } | ||||
/* | /* | ||||
* For dotdot lookups in capability mode, only allow the component | * For dotdot lookups in capability mode, only allow the component | ||||
* lookup to succeed if the resulting directory was already traversed | * lookup to succeed if the resulting directory was already traversed | ||||
* during the operation. This catches situations where already | * during the operation. This catches situations where already | ||||
* traversed directory is moved to different parent, and then we walk | * traversed directory is moved to different parent, and then we walk | ||||
* over it with dotdots. | * over it with dotdots. | ||||
* | * | ||||
* Also allow to force failure of dotdot lookups for non-local | * Also allow to force failure of dotdot lookups for non-local | ||||
* filesystems, where external agents might assist local lookups to | * filesystems, where external agents might assist local lookups to | ||||
* escape the compartment. | * escape the compartment. | ||||
*/ | */ | ||||
static int | static int | ||||
nameicap_check_dotdot(struct nameidata *ndp, struct vnode *dp) | nameicap_check_dotdot(struct nameidata *ndp, struct vnode *dp) | ||||
{ | { | ||||
struct nameicap_tracker *nt; | struct nameicap_tracker *nt; | ||||
struct mount *mp; | struct mount *mp; | ||||
if ((ndp->ni_lcf & NI_LCF_CAP_DOTDOT) == 0 || dp == NULL || | if (dp == NULL || dp->v_type != VDIR || (ndp->ni_lcf & | ||||
dp->v_type != VDIR) | NI_LCF_STRICTRELATIVE) == 0) | ||||
return (0); | return (0); | ||||
if ((ndp->ni_lcf & NI_LCF_CAP_DOTDOT) == 0) | |||||
return (ENOTCAPABLE); | |||||
mp = dp->v_mount; | mp = dp->v_mount; | ||||
if (lookup_cap_dotdot_nonlocal == 0 && mp != NULL && | if (lookup_cap_dotdot_nonlocal == 0 && mp != NULL && | ||||
(mp->mnt_flag & MNT_LOCAL) == 0) | (mp->mnt_flag & MNT_LOCAL) == 0) | ||||
return (ENOTCAPABLE); | return (ENOTCAPABLE); | ||||
TAILQ_FOREACH_REVERSE(nt, &ndp->ni_cap_tracker, nameicap_tracker_head, | TAILQ_FOREACH_REVERSE(nt, &ndp->ni_cap_tracker, nameicap_tracker_head, | ||||
nm_link) { | nm_link) { | ||||
if ((ndp->ni_lcf & NI_LCF_LATCH) != 0 && | if (dp == nt->dp) { | ||||
ndp->ni_beneath_latch == nt->dp) { | nt = TAILQ_NEXT(nt, nm_link); | ||||
ndp->ni_lcf &= ~NI_LCF_BENEATH_LATCHED; | if (nt != NULL) | ||||
nameicap_cleanup(ndp, false); | nameicap_cleanup_from(ndp, nt); | ||||
return (0); | return (0); | ||||
} | } | ||||
if (dp == nt->dp) | |||||
return (0); | |||||
} | } | ||||
return (ENOTCAPABLE); | return (ENOTCAPABLE); | ||||
markj: I'm having a hard time understanding this loop. If `TAILQ_NEXT(nt, nm_link)` is NULL then we… | |||||
Done Inline ActionsRight, I forgot about strange quirk with nt == NULL for TAILQ_FOREACH_FROM_SAFE(). Intent is to clean everything below the current entry. kib: Right, I forgot about strange quirk with nt == NULL for TAILQ_FOREACH_FROM_SAFE().
Intent is… | |||||
} | } | ||||
static void | static void | ||||
namei_cleanup_cnp(struct componentname *cnp) | namei_cleanup_cnp(struct componentname *cnp) | ||||
{ | { | ||||
uma_zfree(namei_zone, cnp->cn_pnbuf); | uma_zfree(namei_zone, cnp->cn_pnbuf); | ||||
#ifdef DIAGNOSTIC | #ifdef DIAGNOSTIC | ||||
Show All 10 Lines | namei_handle_root(struct nameidata *ndp, struct vnode **dpp) | ||||
cnp = &ndp->ni_cnd; | cnp = &ndp->ni_cnd; | ||||
if ((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) != 0) { | if ((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) != 0) { | ||||
#ifdef KTRACE | #ifdef KTRACE | ||||
if (KTRPOINT(curthread, KTR_CAPFAIL)) | if (KTRPOINT(curthread, KTR_CAPFAIL)) | ||||
ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | ||||
#endif | #endif | ||||
return (ENOTCAPABLE); | return (ENOTCAPABLE); | ||||
} | } | ||||
if ((cnp->cn_flags & BENEATH) != 0) { | |||||
ndp->ni_lcf |= NI_LCF_BENEATH_ABS; | |||||
ndp->ni_lcf &= ~NI_LCF_BENEATH_LATCHED; | |||||
nameicap_cleanup(ndp, false); | |||||
} | |||||
while (*(cnp->cn_nameptr) == '/') { | while (*(cnp->cn_nameptr) == '/') { | ||||
cnp->cn_nameptr++; | cnp->cn_nameptr++; | ||||
ndp->ni_pathlen--; | ndp->ni_pathlen--; | ||||
} | } | ||||
*dpp = ndp->ni_rootdir; | *dpp = ndp->ni_rootdir; | ||||
vrefact(*dpp); | vrefact(*dpp); | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
namei_setup(struct nameidata *ndp, struct vnode **dpp, struct pwd **pwdp) | namei_setup(struct nameidata *ndp, struct vnode **dpp, struct pwd **pwdp) | ||||
{ | { | ||||
struct componentname *cnp; | struct componentname *cnp; | ||||
struct file *dfp; | struct file *dfp; | ||||
struct thread *td; | struct thread *td; | ||||
struct pwd *pwd; | struct pwd *pwd; | ||||
cap_rights_t rights; | cap_rights_t rights; | ||||
struct filecaps dirfd_caps; | |||||
int error; | int error; | ||||
bool startdir_used; | bool startdir_used; | ||||
cnp = &ndp->ni_cnd; | cnp = &ndp->ni_cnd; | ||||
td = cnp->cn_thread; | td = cnp->cn_thread; | ||||
startdir_used = false; | startdir_used = false; | ||||
*pwdp = NULL; | *pwdp = NULL; | ||||
▲ Show 20 Lines • Show All 96 Lines • ▼ Show 20 Lines | #ifdef CAPABILITIES | ||||
ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; | ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; | ||||
ndp->ni_resflags |= NIRES_STRICTREL; | ndp->ni_resflags |= NIRES_STRICTREL; | ||||
} | } | ||||
#endif | #endif | ||||
} | } | ||||
if (error == 0 && (*dpp)->v_type != VDIR) | if (error == 0 && (*dpp)->v_type != VDIR) | ||||
error = ENOTDIR; | error = ENOTDIR; | ||||
} | } | ||||
if (error == 0 && (cnp->cn_flags & BENEATH) != 0) { | |||||
if (ndp->ni_dirfd == AT_FDCWD) { | |||||
ndp->ni_beneath_latch = pwd->pwd_cdir; | |||||
vrefact(ndp->ni_beneath_latch); | |||||
} else { | |||||
rights = *ndp->ni_rightsneeded; | |||||
cap_rights_set_one(&rights, CAP_LOOKUP); | |||||
error = fgetvp_rights(td, ndp->ni_dirfd, &rights, | |||||
&dirfd_caps, &ndp->ni_beneath_latch); | |||||
if (error == 0 && (*dpp)->v_type != VDIR) { | |||||
vrele(ndp->ni_beneath_latch); | |||||
error = ENOTDIR; | |||||
} | |||||
} | |||||
if (error == 0) | |||||
ndp->ni_lcf |= NI_LCF_LATCH; | |||||
} | |||||
if (error == 0 && (cnp->cn_flags & RBENEATH) != 0) { | if (error == 0 && (cnp->cn_flags & RBENEATH) != 0) { | ||||
if (cnp->cn_pnbuf[0] == '/' || | if (cnp->cn_pnbuf[0] == '/') { | ||||
(ndp->ni_lcf & NI_LCF_BENEATH_ABS) != 0) { | |||||
error = EINVAL; | error = EINVAL; | ||||
markjUnsubmitted Done Inline ActionsShouldn't this be ENOTCAPABLE? The man page does not really clarify since it describes ENOTCAPABLE only in the context of relative paths, but at least the capsicum test suite expects ENOTCAPABLE for O_BENEATH here. markj: Shouldn't this be ENOTCAPABLE? The man page does not really clarify since it describes… | |||||
} else if ((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) == 0) { | } else if ((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) == 0) { | ||||
ndp->ni_lcf |= NI_LCF_STRICTRELATIVE | | ndp->ni_lcf |= NI_LCF_STRICTRELATIVE | | ||||
NI_LCF_CAP_DOTDOT; | NI_LCF_CAP_DOTDOT; | ||||
} | } | ||||
} | } | ||||
/* | /* | ||||
* If we are auditing the kernel pathname, save the user pathname. | * If we are auditing the kernel pathname, save the user pathname. | ||||
*/ | */ | ||||
if (cnp->cn_flags & AUDITVNODE1) | if (cnp->cn_flags & AUDITVNODE1) | ||||
AUDIT_ARG_UPATH1_VP(td, ndp->ni_rootdir, *dpp, cnp->cn_pnbuf); | AUDIT_ARG_UPATH1_VP(td, ndp->ni_rootdir, *dpp, cnp->cn_pnbuf); | ||||
if (cnp->cn_flags & AUDITVNODE2) | if (cnp->cn_flags & AUDITVNODE2) | ||||
AUDIT_ARG_UPATH2_VP(td, ndp->ni_rootdir, *dpp, cnp->cn_pnbuf); | AUDIT_ARG_UPATH2_VP(td, ndp->ni_rootdir, *dpp, cnp->cn_pnbuf); | ||||
if (ndp->ni_startdir != NULL && !startdir_used) | if (ndp->ni_startdir != NULL && !startdir_used) | ||||
vrele(ndp->ni_startdir); | vrele(ndp->ni_startdir); | ||||
if (error != 0) { | if (error != 0) { | ||||
if (*dpp != NULL) | if (*dpp != NULL) | ||||
vrele(*dpp); | vrele(*dpp); | ||||
pwd_drop(pwd); | pwd_drop(pwd); | ||||
return (error); | return (error); | ||||
} | } | ||||
MPASS((ndp->ni_lcf & (NI_LCF_BENEATH_ABS | NI_LCF_LATCH)) != | if ((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) != 0 && | ||||
NI_LCF_BENEATH_ABS); | lookup_cap_dotdot != 0) | ||||
if (((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) != 0 && | |||||
lookup_cap_dotdot != 0) || | |||||
((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) == 0 && | |||||
(cnp->cn_flags & BENEATH) != 0)) | |||||
ndp->ni_lcf |= NI_LCF_CAP_DOTDOT; | ndp->ni_lcf |= NI_LCF_CAP_DOTDOT; | ||||
SDT_PROBE4(vfs, namei, lookup, entry, *dpp, cnp->cn_pnbuf, | SDT_PROBE4(vfs, namei, lookup, entry, *dpp, cnp->cn_pnbuf, | ||||
cnp->cn_flags, false); | cnp->cn_flags, false); | ||||
*pwdp = pwd; | *pwdp = pwd; | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | static int | ||||
▲ Show 20 Lines • Show All 162 Lines • ▼ Show 20 Lines | #endif | ||||
} | } | ||||
/* | /* | ||||
* Locked lookup. | * Locked lookup. | ||||
*/ | */ | ||||
for (;;) { | for (;;) { | ||||
ndp->ni_startdir = dp; | ndp->ni_startdir = dp; | ||||
error = lookup(ndp); | error = lookup(ndp); | ||||
if (error != 0) { | if (error != 0) | ||||
/* | |||||
* Override an error to not allow user to use | |||||
* BENEATH as an oracle. | |||||
*/ | |||||
if ((ndp->ni_lcf & (NI_LCF_LATCH | | |||||
NI_LCF_BENEATH_LATCHED)) == NI_LCF_LATCH) | |||||
error = ENOTCAPABLE; | |||||
goto out; | goto out; | ||||
} | |||||
/* | /* | ||||
* If not a symbolic link, we're done. | * If not a symbolic link, we're done. | ||||
*/ | */ | ||||
if ((cnp->cn_flags & ISSYMLINK) == 0) { | if ((cnp->cn_flags & ISSYMLINK) == 0) { | ||||
SDT_PROBE4(vfs, namei, lookup, return, error, | SDT_PROBE4(vfs, namei, lookup, return, error, | ||||
(error == 0 ? ndp->ni_vp : NULL), false, ndp); | (error == 0 ? ndp->ni_vp : NULL), false, ndp); | ||||
if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0) { | if ((cnp->cn_flags & (SAVENAME | SAVESTART)) == 0) { | ||||
namei_cleanup_cnp(cnp); | namei_cleanup_cnp(cnp); | ||||
} else | } else | ||||
cnp->cn_flags |= HASBUF; | cnp->cn_flags |= HASBUF; | ||||
if ((ndp->ni_lcf & (NI_LCF_LATCH | | nameicap_cleanup(ndp); | ||||
NI_LCF_BENEATH_LATCHED)) == NI_LCF_LATCH) { | |||||
NDFREE(ndp, 0); | |||||
error = ENOTCAPABLE; | |||||
} | |||||
nameicap_cleanup(ndp, true); | |||||
pwd_drop(pwd); | pwd_drop(pwd); | ||||
if (error == 0) | if (error == 0) | ||||
NDVALIDATE(ndp); | NDVALIDATE(ndp); | ||||
return (error); | return (error); | ||||
} | } | ||||
if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { | if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { | ||||
error = ELOOP; | error = ELOOP; | ||||
break; | break; | ||||
▲ Show 20 Lines • Show All 60 Lines • ▼ Show 20 Lines | #endif | ||||
} | } | ||||
vput(ndp->ni_vp); | vput(ndp->ni_vp); | ||||
ndp->ni_vp = NULL; | ndp->ni_vp = NULL; | ||||
vrele(ndp->ni_dvp); | vrele(ndp->ni_dvp); | ||||
out: | out: | ||||
MPASS(error != 0); | MPASS(error != 0); | ||||
SDT_PROBE4(vfs, namei, lookup, return, error, NULL, false, ndp); | SDT_PROBE4(vfs, namei, lookup, return, error, NULL, false, ndp); | ||||
namei_cleanup_cnp(cnp); | namei_cleanup_cnp(cnp); | ||||
nameicap_cleanup(ndp, true); | nameicap_cleanup(ndp); | ||||
pwd_drop(pwd); | pwd_drop(pwd); | ||||
return (error); | return (error); | ||||
} | } | ||||
static int | static int | ||||
compute_cn_lkflags(struct mount *mp, int lkflags, int cnflags) | compute_cn_lkflags(struct mount *mp, int lkflags, int cnflags) | ||||
{ | { | ||||
▲ Show 20 Lines • Show All 558 Lines • ▼ Show 20 Lines | if (needs_exclusive_leaf(dp->v_mount, cnp->cn_flags) && | ||||
VOP_ISLOCKED(dp) != LK_EXCLUSIVE) { | VOP_ISLOCKED(dp) != LK_EXCLUSIVE) { | ||||
vn_lock(dp, LK_UPGRADE | LK_RETRY); | vn_lock(dp, LK_UPGRADE | LK_RETRY); | ||||
if (VN_IS_DOOMED(dp)) { | if (VN_IS_DOOMED(dp)) { | ||||
error = ENOENT; | error = ENOENT; | ||||
goto bad2; | goto bad2; | ||||
} | } | ||||
} | } | ||||
if (ndp->ni_vp != NULL) { | if (ndp->ni_vp != NULL) { | ||||
if ((cnp->cn_flags & ISDOTDOT) == 0) | |||||
nameicap_tracker_add(ndp, ndp->ni_vp); | nameicap_tracker_add(ndp, ndp->ni_vp); | ||||
if ((cnp->cn_flags & (FAILIFEXISTS | ISSYMLINK)) == FAILIFEXISTS) | if ((cnp->cn_flags & (FAILIFEXISTS | ISSYMLINK)) == FAILIFEXISTS) | ||||
goto bad_eexist; | goto bad_eexist; | ||||
} | } | ||||
return (0); | return (0); | ||||
bad2: | bad2: | ||||
if (ni_dvp_unlocked != 2) { | if (ni_dvp_unlocked != 2) { | ||||
if (dp != ndp->ni_dvp && !ni_dvp_unlocked) | if (dp != ndp->ni_dvp && !ni_dvp_unlocked) | ||||
▲ Show 20 Lines • Show All 422 Lines • Show Last 20 Lines |
I'm having a hard time understanding this loop. If TAILQ_NEXT(nt, nm_link) is NULL then we will traverse the whole list. Is it intentional?