Changeset View
Standalone View
sys/kern/vfs_lookup.c
Show First 20 Lines • Show All 314 Lines • ▼ Show 20 Lines | #ifdef CAPABILITY_MODE | ||||
* components lookups result in the directories which were | * components lookups result in the directories which were | ||||
* previously walked by us, which prevents an escape from | * previously walked by us, which prevents an escape from | ||||
* the relative root. | * the relative root. | ||||
*/ | */ | ||||
if (IN_CAPABILITY_MODE(td) && (cnp->cn_flags & NOCAPCHECK) == 0) { | if (IN_CAPABILITY_MODE(td) && (cnp->cn_flags & NOCAPCHECK) == 0) { | ||||
ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; | ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; | ||||
ndp->ni_resflags |= NIRES_STRICTREL; | ndp->ni_resflags |= NIRES_STRICTREL; | ||||
if (ndp->ni_dirfd == AT_FDCWD) { | if (ndp->ni_dirfd == AT_FDCWD) { | ||||
#ifdef KTRACE | #ifdef KTRACE | ||||
markj: I believe it should not be allowed: `fooat(AT_FDCWD, "", AT_EMPTY_PATH)` is an operation on the… | |||||
if (KTRPOINT(td, KTR_CAPFAIL)) | if (KTRPOINT(td, KTR_CAPFAIL)) | ||||
ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | ||||
#endif | #endif | ||||
return (ECAPMODE); | return (ECAPMODE); | ||||
} | } | ||||
} | } | ||||
#endif | #endif | ||||
error = 0; | error = 0; | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | #ifdef CAPABILITIES | ||||
&rights) || | &rights) || | ||||
ndp->ni_filecaps.fc_fcntls != CAP_FCNTL_ALL || | ndp->ni_filecaps.fc_fcntls != CAP_FCNTL_ALL || | ||||
ndp->ni_filecaps.fc_nioctls != -1) { | ndp->ni_filecaps.fc_nioctls != -1) { | ||||
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 && | ||||
(cnp->cn_pnbuf[0] != '\0' || | |||||
(cnp->cn_flags & EMPTYPATH) == 0)) | |||||
error = ENOTDIR; | error = ENOTDIR; | ||||
} | } | ||||
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] == '/') { | ||||
error = ENOTCAPABLE; | error = ENOTCAPABLE; | ||||
} 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; | ||||
Show All 40 Lines | namei_getpath(struct nameidata *ndp) | ||||
if (ndp->ni_segflg == UIO_SYSSPACE) { | if (ndp->ni_segflg == UIO_SYSSPACE) { | ||||
error = copystr(ndp->ni_dirp, cnp->cn_pnbuf, MAXPATHLEN, | error = copystr(ndp->ni_dirp, cnp->cn_pnbuf, MAXPATHLEN, | ||||
&ndp->ni_pathlen); | &ndp->ni_pathlen); | ||||
} else { | } else { | ||||
error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, MAXPATHLEN, | error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, MAXPATHLEN, | ||||
&ndp->ni_pathlen); | &ndp->ni_pathlen); | ||||
} | } | ||||
if (__predict_false(error != 0)) { | if (__predict_false(error != 0)) | ||||
namei_cleanup_cnp(cnp); | |||||
return (error); | return (error); | ||||
} | |||||
/* | /* | ||||
* Don't allow empty pathnames. | * Don't allow empty pathnames unless EMPTYPATH is specified. | ||||
* Caller checks for ENOENT as an indication for the empty path. | |||||
Done Inline ActionsThe comment should be updated to note that EMPTYPATH is handled in the caller. Otherwise it looks like the code is wrong. markj: The comment should be updated to note that EMPTYPATH is handled in the caller. Otherwise it… | |||||
*/ | */ | ||||
if (__predict_false(*cnp->cn_pnbuf == '\0')) { | if (__predict_false(*cnp->cn_pnbuf == '\0')) | ||||
namei_cleanup_cnp(cnp); | |||||
return (ENOENT); | return (ENOENT); | ||||
Done Inline ActionsI think a comment explaining how this is handled by the caller would be useful. Else it looks like a bug. I would go further and centralize error handling at the end of the function: cnp->cn_nameptr = cnp->cn_pnbuf; return (0); fail: if (error != ENOENT) namei_cleanup_cnp(cnp); return (error); Otherwise this is IMO too fragile. markj: I think a comment explaining how this is handled by the caller would be useful. Else it looks… | |||||
Done Inline ActionsI restructured the error handling to do cleanup in the caller instead. Also added namei_emptypath() and reduced indentations. kib: I restructured the error handling to do cleanup in the caller instead. Also added… | |||||
} | |||||
cnp->cn_nameptr = cnp->cn_pnbuf; | cnp->cn_nameptr = cnp->cn_pnbuf; | ||||
return (0); | return (0); | ||||
} | } | ||||
static int | |||||
namei_emptypath(struct nameidata *ndp) | |||||
{ | |||||
struct componentname *cnp; | |||||
struct pwd *pwd; | |||||
struct vnode *dp; | |||||
int error; | |||||
cnp = &ndp->ni_cnd; | |||||
MPASS(*cnp->cn_pnbuf == '\0'); | |||||
MPASS((cnp->cn_flags & EMPTYPATH) != 0); | |||||
MPASS((cnp->cn_flags & (LOCKPARENT | WANTPARENT)) == 0); | |||||
error = namei_setup(ndp, &dp, &pwd); | |||||
if (error != 0) { | |||||
namei_cleanup_cnp(cnp); | |||||
goto errout; | |||||
} | |||||
ndp->ni_vp = dp; | |||||
vref(dp); | |||||
namei_cleanup_cnp(cnp); | |||||
pwd_drop(pwd); | |||||
ndp->ni_resflags |= NIRES_EMPTYPATH; | |||||
NDVALIDATE(ndp); | |||||
if ((cnp->cn_flags & LOCKLEAF) != 0) { | |||||
VOP_LOCK(dp, (cnp->cn_flags & LOCKSHARED) != 0 ? | |||||
LK_SHARED : LK_EXCLUSIVE); | |||||
if (VN_IS_DOOMED(dp)) { | |||||
vput(dp); | |||||
error = ENOENT; | |||||
goto errout; | |||||
} | |||||
} | |||||
SDT_PROBE4(vfs, namei, lookup, return, 0, ndp->ni_vp, false, ndp); | |||||
return (0); | |||||
errout: | |||||
SDT_PROBE4(vfs, namei, lookup, return, error, NULL, false, ndp); | |||||
return (error); | |||||
} | |||||
/* | /* | ||||
* Convert a pathname into a pointer to a locked vnode. | * Convert a pathname into a pointer to a locked vnode. | ||||
* | * | ||||
* The FOLLOW flag is set when symbolic links are to be followed | * The FOLLOW flag is set when symbolic links are to be followed | ||||
* when they occur at the end of the name translation process. | * when they occur at the end of the name translation process. | ||||
* Symbolic links are always followed for all other pathname | * Symbolic links are always followed for all other pathname | ||||
* components other than the last. | * components other than the last. | ||||
* | * | ||||
▲ Show 20 Lines • Show All 64 Lines • ▼ Show 20 Lines | MPASS(ndp->ni_startdir == NULL || ndp->ni_startdir->v_type == VDIR || | ||||
ndp->ni_startdir->v_type == VBAD); | ndp->ni_startdir->v_type == VBAD); | ||||
ndp->ni_lcf = 0; | ndp->ni_lcf = 0; | ||||
ndp->ni_loopcnt = 0; | ndp->ni_loopcnt = 0; | ||||
ndp->ni_vp = NULL; | ndp->ni_vp = NULL; | ||||
error = namei_getpath(ndp); | error = namei_getpath(ndp); | ||||
if (__predict_false(error != 0)) { | if (__predict_false(error != 0)) { | ||||
if (error == ENOENT && (cnp->cn_flags & EMPTYPATH) != 0) | |||||
return (namei_emptypath(ndp)); | |||||
namei_cleanup_cnp(cnp); | |||||
SDT_PROBE4(vfs, namei, lookup, return, error, NULL, | |||||
false, ndp); | |||||
return (error); | return (error); | ||||
} | } | ||||
#ifdef KTRACE | #ifdef KTRACE | ||||
if (KTRPOINT(td, KTR_NAMEI)) { | if (KTRPOINT(td, KTR_NAMEI)) { | ||||
KASSERT(cnp->cn_thread == curthread, | KASSERT(cnp->cn_thread == curthread, | ||||
("namei not using curthread")); | ("namei not using curthread")); | ||||
ktrnamei(cnp->cn_pnbuf); | ktrnamei(cnp->cn_pnbuf); | ||||
} | } | ||||
#endif | #endif | ||||
/* | /* | ||||
* First try looking up the target without locking any vnodes. | * First try looking up the target without locking any vnodes. | ||||
* | * | ||||
* We may need to start from scratch or pick up where it left off. | * We may need to start from scratch or pick up where it left off. | ||||
*/ | */ | ||||
error = cache_fplookup(ndp, &status, &pwd); | error = cache_fplookup(ndp, &status, &pwd); | ||||
switch (status) { | switch (status) { | ||||
case CACHE_FPL_STATUS_UNSET: | case CACHE_FPL_STATUS_UNSET: | ||||
__assert_unreachable(); | __assert_unreachable(); | ||||
break; | break; | ||||
case CACHE_FPL_STATUS_HANDLED: | case CACHE_FPL_STATUS_HANDLED: | ||||
if (error == 0) | if (error == 0) | ||||
NDVALIDATE(ndp); | NDVALIDATE(ndp); | ||||
Done Inline ActionsIMO it would be nicer to lift this into a separate function. There is too much indentation otherwise. markj: IMO it would be nicer to lift this into a separate function. There is too much indentation… | |||||
return (error); | return (error); | ||||
case CACHE_FPL_STATUS_PARTIAL: | case CACHE_FPL_STATUS_PARTIAL: | ||||
TAILQ_INIT(&ndp->ni_cap_tracker); | TAILQ_INIT(&ndp->ni_cap_tracker); | ||||
dp = ndp->ni_startdir; | dp = ndp->ni_startdir; | ||||
break; | break; | ||||
case CACHE_FPL_STATUS_DESTROYED: | case CACHE_FPL_STATUS_DESTROYED: | ||||
ndp->ni_loopcnt = 0; | ndp->ni_loopcnt = 0; | ||||
error = namei_getpath(ndp); | error = namei_getpath(ndp); | ||||
if (__predict_false(error != 0)) { | if (__predict_false(error != 0)) { | ||||
namei_cleanup_cnp(cnp); | |||||
return (error); | return (error); | ||||
Done Inline ActionsCleanup now needs to happen here too, no? markj: Cleanup now needs to happen here too, no? | |||||
Done Inline ActionsI think that no. To get there, we must already pass namei_getpath() above, and then ENOENT or any other error is already handled. Hmm, indeed, userspace might change the string between previous and this namei_getpath(). Ok. kib: I think that no. To get there, we must already pass namei_getpath() above, and then ENOENT or… | |||||
} | } | ||||
/* FALLTHROUGH */ | /* FALLTHROUGH */ | ||||
case CACHE_FPL_STATUS_ABORTED: | case CACHE_FPL_STATUS_ABORTED: | ||||
TAILQ_INIT(&ndp->ni_cap_tracker); | TAILQ_INIT(&ndp->ni_cap_tracker); | ||||
MPASS(ndp->ni_lcf == 0); | MPASS(ndp->ni_lcf == 0); | ||||
error = namei_setup(ndp, &dp, &pwd); | error = namei_setup(ndp, &dp, &pwd); | ||||
if (error != 0) { | if (error != 0) { | ||||
namei_cleanup_cnp(cnp); | namei_cleanup_cnp(cnp); | ||||
▲ Show 20 Lines • Show All 1,116 Lines • Show Last 20 Lines |
I believe it should not be allowed: fooat(AT_FDCWD, "", AT_EMPTY_PATH) is an operation on the CWD, but the CWD is not a capability. fooat(AT_FDCWD) is always disallowed in capability mode.