Changeset View
Standalone View
sys/kern/vfs_lookup.c
Show First 20 Lines • Show All 67 Lines • ▼ Show 20 Lines | |||||
#define NAMEI_DIAGNOSTIC 1 | #define NAMEI_DIAGNOSTIC 1 | ||||
#undef NAMEI_DIAGNOSTIC | #undef NAMEI_DIAGNOSTIC | ||||
SDT_PROVIDER_DECLARE(vfs); | SDT_PROVIDER_DECLARE(vfs); | ||||
SDT_PROBE_DEFINE3(vfs, namei, lookup, entry, "struct vnode *", "char *", | SDT_PROBE_DEFINE3(vfs, namei, lookup, entry, "struct vnode *", "char *", | ||||
"unsigned long"); | "unsigned long"); | ||||
SDT_PROBE_DEFINE2(vfs, namei, lookup, return, "int", "struct vnode *"); | SDT_PROBE_DEFINE2(vfs, namei, lookup, return, "int", "struct vnode *"); | ||||
/* | /* Allocation zone for namei. */ | ||||
* Allocation zone for namei | |||||
*/ | |||||
uma_zone_t namei_zone; | uma_zone_t namei_zone; | ||||
/* | |||||
* Placeholder vnode for mp traversal | /* Placeholder vnode for mp traversal. */ | ||||
*/ | |||||
static struct vnode *vp_crossmp; | static struct vnode *vp_crossmp; | ||||
struct nameicap_tracker { | |||||
struct vnode *dp; | |||||
TAILQ_ENTRY(nameicap_tracker) nm_link; | |||||
}; | |||||
/* Zone for cap mode tracker elements used for dotdot capability checks. */ | |||||
static uma_zone_t nt_zone; | |||||
static void | static void | ||||
nameiinit(void *dummy __unused) | nameiinit(void *dummy __unused) | ||||
{ | { | ||||
namei_zone = uma_zcreate("NAMEI", MAXPATHLEN, NULL, NULL, NULL, NULL, | namei_zone = uma_zcreate("NAMEI", MAXPATHLEN, NULL, NULL, NULL, NULL, | ||||
UMA_ALIGN_PTR, 0); | UMA_ALIGN_PTR, 0); | ||||
nt_zone = uma_zcreate("rentr", sizeof(struct nameicap_tracker), | |||||
rwatson: I wonder if "nameicap_tracker" or similar might be a better string than "rentr"? | |||||
Not Done Inline ActionsI highly dislike long (or worse, containing space-delimited words) names for zones. Both vmstat -z and ddb 'show uma' become hard to read and parse. Anybody who looks at the zone needs to grep the source anyway. kib: I highly dislike long (or worse, containing space-delimited words) names for zones. Both… | |||||
NULL, NULL, NULL, NULL, sizeof(void *), 0); | |||||
getnewvnode("crossmp", NULL, &dead_vnodeops, &vp_crossmp); | getnewvnode("crossmp", NULL, &dead_vnodeops, &vp_crossmp); | ||||
vn_lock(vp_crossmp, LK_EXCLUSIVE); | vn_lock(vp_crossmp, LK_EXCLUSIVE); | ||||
VN_LOCK_ASHARE(vp_crossmp); | VN_LOCK_ASHARE(vp_crossmp); | ||||
VOP_UNLOCK(vp_crossmp, 0); | VOP_UNLOCK(vp_crossmp, 0); | ||||
} | } | ||||
SYSINIT(vfs, SI_SUB_VFS, SI_ORDER_SECOND, nameiinit, NULL); | SYSINIT(vfs, SI_SUB_VFS, SI_ORDER_SECOND, nameiinit, NULL); | ||||
static int lookup_shared = 1; | static int lookup_shared = 1; | ||||
SYSCTL_INT(_vfs, OID_AUTO, lookup_shared, CTLFLAG_RWTUN, &lookup_shared, 0, | SYSCTL_INT(_vfs, OID_AUTO, lookup_shared, CTLFLAG_RWTUN, &lookup_shared, 0, | ||||
"Enables/Disables shared locks for path name translation"); | "enables shared locks for path name translation"); | ||||
/* | |||||
* Intent is that lookup_cap_dotdot becomes unconditionally enabled, | |||||
* but it defaults to the disabled state until verification efforts | |||||
Not Done Inline ActionsMaybe enable \"..\" components in path lookup in capability mode? I think just "enable" is canonical, and nice to be shorter. You may want to also add a comment before this that the intent is this becomes unconditionally enabled, but that a sysctl is provided until verification efforts are complete. emaste: Maybe `enable \"..\" components in path lookup in capability mode`? I think just "enable" is… | |||||
* are complete. | |||||
*/ | |||||
static int lookup_cap_dotdot = 0; | |||||
SYSCTL_INT(_vfs, OID_AUTO, lookup_cap_dotdot, CTLFLAG_RWTUN, | |||||
&lookup_cap_dotdot, 0, | |||||
"enables \"..\" components in path lookup in capability mode"); | |||||
static void | static void | ||||
nameicap_tracker_add(struct nameidata *ndp, struct vnode *dp) | |||||
{ | |||||
struct nameicap_tracker *nt; | |||||
if ((ndp->ni_lcf & NI_LCF_CAP_DOTDOT) == 0 || dp->v_type != VDIR) | |||||
return; | |||||
nt = uma_zalloc(nt_zone, M_WAITOK); | |||||
vhold(dp); | |||||
nt->dp = dp; | |||||
TAILQ_INSERT_TAIL(&ndp->ni_cap_tracker, nt, nm_link); | |||||
} | |||||
static void | |||||
nameicap_cleanup(struct nameidata *ndp) | |||||
{ | |||||
struct nameicap_tracker *nt, *nt1; | |||||
KASSERT(TAILQ_EMPTY(&ndp->ni_cap_tracker) || | |||||
(ndp->ni_lcf & NI_LCF_CAP_DOTDOT) != 0, ("not strictrelative")); | |||||
TAILQ_FOREACH_SAFE(nt, &ndp->ni_cap_tracker, nm_link, nt1) { | |||||
TAILQ_REMOVE(&ndp->ni_cap_tracker, nt, nm_link); | |||||
vdrop(nt->dp); | |||||
uma_zfree(nt_zone, nt); | |||||
} | |||||
} | |||||
/* | |||||
* For dotdot lookups in capability mode, only allow the component | |||||
* lookup to succeed if the resulting directory was already traversed | |||||
* during the operation. Also fail dotdot lookups for non-local | |||||
* filesystems, where external agents might assist local lookups to | |||||
* escape the compartment. | |||||
*/ | |||||
static int | |||||
nameicap_check_dotdot(struct nameidata *ndp, struct vnode *dp) | |||||
{ | |||||
struct nameicap_tracker *nt; | |||||
struct mount *mp; | |||||
if ((ndp->ni_lcf & NI_LCF_CAP_DOTDOT) == 0 || dp == NULL || | |||||
dp->v_type != VDIR) | |||||
return (0); | |||||
mp = dp->v_mount; | |||||
if (mp != NULL && (mp->mnt_flag & MNT_LOCAL) == 0) | |||||
return (ENOTCAPABLE); | |||||
TAILQ_FOREACH_REVERSE(nt, &ndp->ni_cap_tracker, nameicap_tracker_head, | |||||
nm_link) { | |||||
if (dp == nt->dp) | |||||
return (0); | |||||
} | |||||
return (ENOTCAPABLE); | |||||
} | |||||
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 | ||||
cnp->cn_pnbuf = NULL; | cnp->cn_pnbuf = NULL; | ||||
cnp->cn_nameptr = NULL; | cnp->cn_nameptr = NULL; | ||||
#endif | #endif | ||||
} | } | ||||
static int | static int | ||||
namei_handle_root(struct nameidata *ndp, struct vnode **dpp) | namei_handle_root(struct nameidata *ndp, struct vnode **dpp) | ||||
{ | { | ||||
struct componentname *cnp; | struct componentname *cnp; | ||||
cnp = &ndp->ni_cnd; | cnp = &ndp->ni_cnd; | ||||
if (ndp->ni_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); | ||||
} | } | ||||
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; | ||||
VREF(*dpp); | VREF(*dpp); | ||||
Not Done Inline Actionsperhaps s/resulted/resulting/? rwatson: perhaps s/resulted/resulting/? | |||||
return (0); | return (0); | ||||
} | } | ||||
Not Done Inline ActionsI'm still pondering the rationale for this choice but feel that being conservative is safer. rwatson: I'm still pondering the rationale for this choice but feel that being conservative is safer. | |||||
/* | /* | ||||
* 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. | ||||
* | * | ||||
* The segflg defines whether the name is to be copied from user | * The segflg defines whether the name is to be copied from user | ||||
* space or kernel space. | * space or kernel space. | ||||
* | * | ||||
* Overall outline of namei: | * Overall outline of namei: | ||||
Not Done Inline ActionsMy initial thinking was that this new approach is safe even for !MNT_LOCAL -- do we have reason to think we need to retain this check? rwatson: My initial thinking was that this new approach is safe even for !MNT_LOCAL -- do we have reason… | |||||
Not Done Inline ActionsIt depends on what you consider the adverse behavior on the server ? If you consider it fine to remove the check, I will just do it without arguing. kib: It depends on what you consider the adverse behavior on the server ? If you consider it fine… | |||||
* | * | ||||
* copy in name | * copy in name | ||||
* get starting directory | * get starting directory | ||||
* while (!done && !error) { | * while (!done && !error) { | ||||
* call lookup to search path. | * call lookup to search path. | ||||
* if symbolic link, massage name in buffer and continue | * if symbolic link, massage name in buffer and continue | ||||
* } | * } | ||||
*/ | */ | ||||
int | int | ||||
namei(struct nameidata *ndp) | namei(struct nameidata *ndp) | ||||
{ | { | ||||
struct filedesc *fdp; /* pointer to file descriptor state */ | struct filedesc *fdp; /* pointer to file descriptor state */ | ||||
char *cp; /* pointer into pathname argument */ | char *cp; /* pointer into pathname argument */ | ||||
struct vnode *dp; /* the directory we are searching */ | struct vnode *dp; /* the directory we are searching */ | ||||
struct iovec aiov; /* uio for reading symbolic links */ | struct iovec aiov; /* uio for reading symbolic links */ | ||||
struct componentname *cnp; | |||||
struct thread *td; | |||||
struct proc *p; | |||||
cap_rights_t rights; | |||||
struct uio auio; | struct uio auio; | ||||
int error, linklen, startdir_used; | int error, linklen, startdir_used; | ||||
struct componentname *cnp = &ndp->ni_cnd; | |||||
struct thread *td = cnp->cn_thread; | |||||
struct proc *p = td->td_proc; | |||||
cnp = &ndp->ni_cnd; | |||||
td = cnp->cn_thread; | |||||
p = td->td_proc; | |||||
ndp->ni_cnd.cn_cred = ndp->ni_cnd.cn_thread->td_ucred; | ndp->ni_cnd.cn_cred = ndp->ni_cnd.cn_thread->td_ucred; | ||||
KASSERT(cnp->cn_cred && p, ("namei: bad cred/proc")); | KASSERT(cnp->cn_cred && p, ("namei: bad cred/proc")); | ||||
KASSERT((cnp->cn_nameiop & (~OPMASK)) == 0, | KASSERT((cnp->cn_nameiop & (~OPMASK)) == 0, | ||||
("namei: nameiop contaminated with flags")); | ("namei: nameiop contaminated with flags")); | ||||
KASSERT((cnp->cn_flags & OPMASK) == 0, | KASSERT((cnp->cn_flags & OPMASK) == 0, | ||||
("namei: flags contaminated with nameiops")); | ("namei: flags contaminated with nameiops")); | ||||
MPASS(ndp->ni_startdir == NULL || ndp->ni_startdir->v_type == VDIR || | MPASS(ndp->ni_startdir == NULL || ndp->ni_startdir->v_type == VDIR || | ||||
ndp->ni_startdir->v_type == VBAD); | ndp->ni_startdir->v_type == VBAD); | ||||
if (!lookup_shared) | if (!lookup_shared) | ||||
cnp->cn_flags &= ~LOCKSHARED; | cnp->cn_flags &= ~LOCKSHARED; | ||||
fdp = p->p_fd; | fdp = p->p_fd; | ||||
TAILQ_INIT(&ndp->ni_cap_tracker); | |||||
ndp->ni_lcf = 0; | |||||
/* We will set this ourselves if we need it. */ | /* We will set this ourselves if we need it. */ | ||||
cnp->cn_flags &= ~TRAILINGSLASH; | cnp->cn_flags &= ~TRAILINGSLASH; | ||||
/* | /* | ||||
* Get a buffer for the name to be translated, and copy the | * Get a buffer for the name to be translated, and copy the | ||||
* name into the buffer. | * name into the buffer. | ||||
*/ | */ | ||||
if ((cnp->cn_flags & HASBUF) == 0) | if ((cnp->cn_flags & HASBUF) == 0) | ||||
cnp->cn_pnbuf = uma_zalloc(namei_zone, M_WAITOK); | cnp->cn_pnbuf = uma_zalloc(namei_zone, M_WAITOK); | ||||
if (ndp->ni_segflg == UIO_SYSSPACE) | if (ndp->ni_segflg == UIO_SYSSPACE) | ||||
error = copystr(ndp->ni_dirp, cnp->cn_pnbuf, | error = copystr(ndp->ni_dirp, cnp->cn_pnbuf, MAXPATHLEN, | ||||
MAXPATHLEN, (size_t *)&ndp->ni_pathlen); | &ndp->ni_pathlen); | ||||
else | else | ||||
error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, | error = copyinstr(ndp->ni_dirp, cnp->cn_pnbuf, MAXPATHLEN, | ||||
MAXPATHLEN, (size_t *)&ndp->ni_pathlen); | &ndp->ni_pathlen); | ||||
/* | /* | ||||
* Don't allow empty pathnames. | * Don't allow empty pathnames. | ||||
*/ | */ | ||||
if (error == 0 && *cnp->cn_pnbuf == '\0') | if (error == 0 && *cnp->cn_pnbuf == '\0') | ||||
error = ENOENT; | error = ENOENT; | ||||
#ifdef CAPABILITY_MODE | #ifdef CAPABILITY_MODE | ||||
/* | /* | ||||
* In capability mode, lookups must be "strictly relative" (i.e. | * In capability mode, lookups must be restricted to happen in | ||||
* not an absolute path, and not containing '..' components) to | * the subtree with the root specified by the file descriptor: | ||||
* a real file descriptor, not the pseudo-descriptor AT_FDCWD. | * - The root must be real file descriptor, not the pseudo-descriptor | ||||
Not Done Inline ActionsWith the new approach, we might need to clarify that this is not quite what we implement, although it is the effect of what we implement. rwatson: With the new approach, we might need to clarify that this is not quite what we implement… | |||||
* AT_FDCWD. | |||||
* - The passed path must be relative and not absolute. | |||||
* - If lookup_cap_dotdot is disabled, path must not contain the | |||||
* '..' components. | |||||
* - If lookup_cap_dotdot is enabled, we verify that all '..' | |||||
* components lookups result in the directories which were | |||||
* previously walked by us, which prevents an escape from | |||||
* the relative root. | |||||
*/ | */ | ||||
if (error == 0 && IN_CAPABILITY_MODE(td) && | if (error == 0 && IN_CAPABILITY_MODE(td) && | ||||
(cnp->cn_flags & NOCAPCHECK) == 0) { | (cnp->cn_flags & NOCAPCHECK) == 0) { | ||||
ndp->ni_strictrelative = 1; | ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; | ||||
if (ndp->ni_dirfd == AT_FDCWD) { | if (ndp->ni_dirfd == AT_FDCWD) { | ||||
#ifdef KTRACE | #ifdef KTRACE | ||||
if (KTRPOINT(td, KTR_CAPFAIL)) | if (KTRPOINT(td, KTR_CAPFAIL)) | ||||
ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | ||||
#endif | #endif | ||||
error = ECAPMODE; | error = ECAPMODE; | ||||
} | } | ||||
} | } | ||||
Show All 35 Lines | #endif | ||||
} else { | } else { | ||||
if (ndp->ni_startdir != NULL) { | if (ndp->ni_startdir != NULL) { | ||||
dp = ndp->ni_startdir; | dp = ndp->ni_startdir; | ||||
startdir_used = 1; | startdir_used = 1; | ||||
} else if (ndp->ni_dirfd == AT_FDCWD) { | } else if (ndp->ni_dirfd == AT_FDCWD) { | ||||
dp = fdp->fd_cdir; | dp = fdp->fd_cdir; | ||||
VREF(dp); | VREF(dp); | ||||
} else { | } else { | ||||
cap_rights_t rights; | |||||
rights = ndp->ni_rightsneeded; | rights = ndp->ni_rightsneeded; | ||||
cap_rights_set(&rights, CAP_LOOKUP); | cap_rights_set(&rights, CAP_LOOKUP); | ||||
if (cnp->cn_flags & AUDITVNODE1) | if (cnp->cn_flags & AUDITVNODE1) | ||||
AUDIT_ARG_ATFD1(ndp->ni_dirfd); | AUDIT_ARG_ATFD1(ndp->ni_dirfd); | ||||
if (cnp->cn_flags & AUDITVNODE2) | if (cnp->cn_flags & AUDITVNODE2) | ||||
AUDIT_ARG_ATFD2(ndp->ni_dirfd); | AUDIT_ARG_ATFD2(ndp->ni_dirfd); | ||||
error = fgetvp_rights(td, ndp->ni_dirfd, | error = fgetvp_rights(td, ndp->ni_dirfd, | ||||
&rights, &ndp->ni_filecaps, &dp); | &rights, &ndp->ni_filecaps, &dp); | ||||
if (error == EINVAL) | if (error == EINVAL) | ||||
error = ENOTDIR; | error = ENOTDIR; | ||||
#ifdef CAPABILITIES | #ifdef CAPABILITIES | ||||
/* | /* | ||||
* If file descriptor doesn't have all rights, | * If file descriptor doesn't have all rights, | ||||
* all lookups relative to it must also be | * all lookups relative to it must also be | ||||
* strictly relative. | * strictly relative. | ||||
*/ | */ | ||||
CAP_ALL(&rights); | CAP_ALL(&rights); | ||||
if (!cap_rights_contains(&ndp->ni_filecaps.fc_rights, | if (!cap_rights_contains(&ndp->ni_filecaps.fc_rights, | ||||
&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_strictrelative = 1; | ndp->ni_lcf |= NI_LCF_STRICTRELATIVE; | ||||
} | } | ||||
#endif | #endif | ||||
} | } | ||||
if (error == 0 && dp->v_type != VDIR) | if (error == 0 && dp->v_type != VDIR) | ||||
error = ENOTDIR; | error = ENOTDIR; | ||||
} | } | ||||
FILEDESC_SUNLOCK(fdp); | FILEDESC_SUNLOCK(fdp); | ||||
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 (dp != NULL) | if (dp != NULL) | ||||
vrele(dp); | vrele(dp); | ||||
goto out; | goto out; | ||||
} | } | ||||
if ((ndp->ni_lcf & NI_LCF_STRICTRELATIVE) != 0 && | |||||
lookup_cap_dotdot != 0) | |||||
ndp->ni_lcf |= NI_LCF_CAP_DOTDOT; | |||||
SDT_PROBE3(vfs, namei, lookup, entry, dp, cnp->cn_pnbuf, | SDT_PROBE3(vfs, namei, lookup, entry, dp, cnp->cn_pnbuf, | ||||
cnp->cn_flags); | cnp->cn_flags); | ||||
for (;;) { | for (;;) { | ||||
ndp->ni_startdir = dp; | ndp->ni_startdir = dp; | ||||
error = lookup(ndp); | error = lookup(ndp); | ||||
if (error != 0) | if (error != 0) | ||||
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) { | ||||
vrele(ndp->ni_rootdir); | vrele(ndp->ni_rootdir); | ||||
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; | ||||
nameicap_cleanup(ndp); | |||||
SDT_PROBE2(vfs, namei, lookup, return, 0, ndp->ni_vp); | SDT_PROBE2(vfs, namei, lookup, return, 0, ndp->ni_vp); | ||||
return (0); | return (0); | ||||
} | } | ||||
if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { | if (ndp->ni_loopcnt++ >= MAXSYMLINKS) { | ||||
error = ELOOP; | error = ELOOP; | ||||
break; | break; | ||||
} | } | ||||
#ifdef MAC | #ifdef MAC | ||||
▲ Show 20 Lines • Show All 57 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: | ||||
vrele(ndp->ni_rootdir); | vrele(ndp->ni_rootdir); | ||||
namei_cleanup_cnp(cnp); | namei_cleanup_cnp(cnp); | ||||
nameicap_cleanup(ndp); | |||||
SDT_PROBE2(vfs, namei, lookup, return, error, NULL); | SDT_PROBE2(vfs, namei, lookup, return, error, NULL); | ||||
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 180 Lines • ▼ Show 20 Lines | #endif | ||||
if ((cnp->cn_flags & ISLASTCN) != 0 && | if ((cnp->cn_flags & ISLASTCN) != 0 && | ||||
cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.' && | cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.' && | ||||
(cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { | (cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { | ||||
error = EINVAL; | error = EINVAL; | ||||
goto bad; | goto bad; | ||||
} | } | ||||
nameicap_tracker_add(ndp, dp); | |||||
/* | /* | ||||
* Check for degenerate name (e.g. / or "") | * Check for degenerate name (e.g. / or "") | ||||
* which is a way of talking about a directory, | * which is a way of talking about a directory, | ||||
* e.g. like "/." or ".". | * e.g. like "/." or ".". | ||||
*/ | */ | ||||
if (cnp->cn_nameptr[0] == '\0') { | if (cnp->cn_nameptr[0] == '\0') { | ||||
if (dp->v_type != VDIR) { | if (dp->v_type != VDIR) { | ||||
error = ENOTDIR; | error = ENOTDIR; | ||||
Show All 17 Lines | if (cnp->cn_nameptr[0] == '\0') { | ||||
if (!(cnp->cn_flags & (LOCKPARENT | LOCKLEAF))) | if (!(cnp->cn_flags & (LOCKPARENT | LOCKLEAF))) | ||||
VOP_UNLOCK(dp, 0); | VOP_UNLOCK(dp, 0); | ||||
/* XXX This should probably move to the top of function. */ | /* XXX This should probably move to the top of function. */ | ||||
if (cnp->cn_flags & SAVESTART) | if (cnp->cn_flags & SAVESTART) | ||||
panic("lookup: SAVESTART"); | panic("lookup: SAVESTART"); | ||||
goto success; | goto success; | ||||
} | } | ||||
/* | /* | ||||
Done Inline ActionsIf this were earlier in dirloop, would we be able to remove the call at line 371? jonathan: If this were earlier in `dirloop`, would we be able to remove the call at line 371? | |||||
* Handle "..": five special cases. | * Handle "..": five special cases. | ||||
* 0. If doing a capability lookup, return ENOTCAPABLE (this is a | * 0. If doing a capability lookup and lookup_cap_dotdot is | ||||
* fairly conservative design choice, but it's the only one that we | * disabled, return ENOTCAPABLE. | ||||
* are satisfied guarantees the property we're looking for). | |||||
* 1. Return an error if this is the last component of | * 1. Return an error if this is the last component of | ||||
* the name and the operation is DELETE or RENAME. | * the name and the operation is DELETE or RENAME. | ||||
* 2. If at root directory (e.g. after chroot) | * 2. If at root directory (e.g. after chroot) | ||||
* or at absolute root directory | * or at absolute root directory | ||||
* then ignore it so can't get out. | * then ignore it so can't get out. | ||||
* 3. If this vnode is the root of a mounted | * 3. If this vnode is the root of a mounted | ||||
* filesystem, then replace it with the | * filesystem, then replace it with the | ||||
* vnode which was mounted on so we take the | * vnode which was mounted on so we take the | ||||
* .. in the other filesystem. | * .. in the other filesystem. | ||||
* 4. If the vnode is the top directory of | * 4. If the vnode is the top directory of | ||||
* the jail or chroot, don't let them out. | * the jail or chroot, don't let them out. | ||||
* 5. If doing a capability lookup and lookup_cap_dotdot is | |||||
* enabled, return ENOTCAPABLE if the lookup would escape | |||||
* from the initial file descriptor directory. Checks are | |||||
* done by ensuring that namei() already traversed the | |||||
* result of dotdot lookup. | |||||
*/ | */ | ||||
if (cnp->cn_flags & ISDOTDOT) { | if (cnp->cn_flags & ISDOTDOT) { | ||||
if (ndp->ni_strictrelative != 0) { | if ((ndp->ni_lcf & (NI_LCF_STRICTRELATIVE | NI_LCF_CAP_DOTDOT)) | ||||
== NI_LCF_STRICTRELATIVE) { | |||||
Not Done Inline ActionsThis check is not sufficient to provide security in the presence of colluding concurrent userspace threads (t1, t2, both in capability mode) operating on a shared directory subtree, "a/b/c" and directory descriptors for "a" (afd) and "b" (bfd). When t1 performs an openat(bfd, "c/.."), t2 might simultaneously perform a renameat(afd, "b/c", afd, "c"). If the attacker wins the race, t1's evaluation if "c/.." could reach "a" rather than being blocked at "b", at which point the further lookup of ".." would escape both afd's and bfd's subtrees. Assuming I've described the scenario correctly, anyway! rwatson: This check is not sufficient to provide security in the presence of colluding concurrent… | |||||
#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 | ||||
error = ENOTCAPABLE; | error = ENOTCAPABLE; | ||||
goto bad; | goto bad; | ||||
} | } | ||||
if ((cnp->cn_flags & ISLASTCN) != 0 && | if ((cnp->cn_flags & ISLASTCN) != 0 && | ||||
Show All 25 Lines | for (;;) { | ||||
} | } | ||||
tdp = dp; | tdp = dp; | ||||
dp = dp->v_mount->mnt_vnodecovered; | dp = dp->v_mount->mnt_vnodecovered; | ||||
VREF(dp); | VREF(dp); | ||||
vput(tdp); | vput(tdp); | ||||
vn_lock(dp, | vn_lock(dp, | ||||
compute_cn_lkflags(dp->v_mount, cnp->cn_lkflags | | compute_cn_lkflags(dp->v_mount, cnp->cn_lkflags | | ||||
LK_RETRY, ISDOTDOT)); | LK_RETRY, ISDOTDOT)); | ||||
error = nameicap_check_dotdot(ndp, dp); | |||||
if (error != 0) { | |||||
#ifdef KTRACE | |||||
Not Done Inline ActionsWe always do the same KTrace stuff when nameicap_check_dotdot fails, so could we move the KTrace bits to nameicap_check_dotdot? jonathan: We always do the same KTrace stuff when `nameicap_check_dotdot` fails, so could we move the… | |||||
Not Done Inline ActionsI pondered this but I think I do not want to do the change now. Current structure of nameicap_check_dotdot() is not suitable for the move, since there are two error exits. There are reservations about MNT_LOCAL already stated, I do not want to change the control flow without the decision about local. Additionally, that function is a helper with the single purpose, KTR/probes stuff lives in lookup() itself. kib: I pondered this but I think I do not want to do the change now.
Current structure of… | |||||
if (KTRPOINT(curthread, KTR_CAPFAIL)) | |||||
ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | |||||
#endif | |||||
goto bad; | |||||
} | } | ||||
} | } | ||||
} | |||||
/* | /* | ||||
* We now have a segment name to search for, and a directory to search. | * We now have a segment name to search for, and a directory to search. | ||||
*/ | */ | ||||
unionlookup: | unionlookup: | ||||
#ifdef MAC | #ifdef MAC | ||||
if ((cnp->cn_flags & NOMACCHECK) == 0) { | if ((cnp->cn_flags & NOMACCHECK) == 0) { | ||||
error = mac_vnode_check_lookup(cnp->cn_thread->td_ucred, dp, | error = mac_vnode_check_lookup(cnp->cn_thread->td_ucred, dp, | ||||
▲ Show 20 Lines • Show All 41 Lines • ▼ Show 20 Lines | if ((error == ENOENT) && | ||||
(dp->v_mount->mnt_flag & MNT_UNION)) { | (dp->v_mount->mnt_flag & MNT_UNION)) { | ||||
tdp = dp; | tdp = dp; | ||||
dp = dp->v_mount->mnt_vnodecovered; | dp = dp->v_mount->mnt_vnodecovered; | ||||
VREF(dp); | VREF(dp); | ||||
vput(tdp); | vput(tdp); | ||||
vn_lock(dp, | vn_lock(dp, | ||||
compute_cn_lkflags(dp->v_mount, cnp->cn_lkflags | | compute_cn_lkflags(dp->v_mount, cnp->cn_lkflags | | ||||
LK_RETRY, cnp->cn_flags)); | LK_RETRY, cnp->cn_flags)); | ||||
nameicap_tracker_add(ndp, dp); | |||||
goto unionlookup; | goto unionlookup; | ||||
} | } | ||||
if (error == ERELOOKUP) { | if (error == ERELOOKUP) { | ||||
vref(dp); | vref(dp); | ||||
ndp->ni_vp = dp; | ndp->ni_vp = dp; | ||||
error = 0; | error = 0; | ||||
relookup = 1; | relookup = 1; | ||||
▲ Show 20 Lines • Show All 104 Lines • ▼ Show 20 Lines | nextname: | ||||
if (relookup) { | if (relookup) { | ||||
relookup = 0; | relookup = 0; | ||||
if (ndp->ni_dvp != dp) | if (ndp->ni_dvp != dp) | ||||
vput(ndp->ni_dvp); | vput(ndp->ni_dvp); | ||||
else | else | ||||
vrele(ndp->ni_dvp); | vrele(ndp->ni_dvp); | ||||
goto dirloop; | goto dirloop; | ||||
} | } | ||||
if (cnp->cn_flags & ISDOTDOT) { | |||||
error = nameicap_check_dotdot(ndp, ndp->ni_vp); | |||||
if (error != 0) { | |||||
#ifdef KTRACE | |||||
if (KTRPOINT(curthread, KTR_CAPFAIL)) | |||||
ktrcapfail(CAPFAIL_LOOKUP, NULL, NULL); | |||||
#endif | |||||
goto bad2; | |||||
} | |||||
} | |||||
if (*ndp->ni_next == '/') { | if (*ndp->ni_next == '/') { | ||||
cnp->cn_nameptr = ndp->ni_next; | cnp->cn_nameptr = ndp->ni_next; | ||||
while (*cnp->cn_nameptr == '/') { | while (*cnp->cn_nameptr == '/') { | ||||
cnp->cn_nameptr++; | cnp->cn_nameptr++; | ||||
ndp->ni_pathlen--; | ndp->ni_pathlen--; | ||||
} | } | ||||
if (ndp->ni_dvp != dp) | if (ndp->ni_dvp != dp) | ||||
vput(ndp->ni_dvp); | vput(ndp->ni_dvp); | ||||
▲ Show 20 Lines • Show All 210 Lines • ▼ Show 20 Lines | |||||
{ | { | ||||
ndp->ni_cnd.cn_nameiop = op; | ndp->ni_cnd.cn_nameiop = op; | ||||
ndp->ni_cnd.cn_flags = flags; | ndp->ni_cnd.cn_flags = flags; | ||||
ndp->ni_segflg = segflg; | ndp->ni_segflg = segflg; | ||||
ndp->ni_dirp = namep; | ndp->ni_dirp = namep; | ||||
ndp->ni_dirfd = dirfd; | ndp->ni_dirfd = dirfd; | ||||
ndp->ni_startdir = startdir; | ndp->ni_startdir = startdir; | ||||
ndp->ni_strictrelative = 0; | |||||
if (rightsp != NULL) | if (rightsp != NULL) | ||||
ndp->ni_rightsneeded = *rightsp; | ndp->ni_rightsneeded = *rightsp; | ||||
else | else | ||||
cap_rights_init(&ndp->ni_rightsneeded); | cap_rights_init(&ndp->ni_rightsneeded); | ||||
filecaps_init(&ndp->ni_filecaps); | filecaps_init(&ndp->ni_filecaps); | ||||
ndp->ni_cnd.cn_thread = td; | ndp->ni_cnd.cn_thread = td; | ||||
} | } | ||||
▲ Show 20 Lines • Show All 166 Lines • Show Last 20 Lines |
I wonder if "nameicap_tracker" or similar might be a better string than "rentr"?