Changeset View
Changeset View
Standalone View
Standalone View
sys/kern/vfs_cache.c
| Show First 20 Lines • Show All 3,286 Lines • ▼ Show 20 Lines | kern___realpathat(struct thread *td, int fd, const char *path, char *buf, | ||||
| if (flags != 0) | if (flags != 0) | ||||
| return (EINVAL); | return (EINVAL); | ||||
| NDINIT_ATRIGHTS(&nd, LOOKUP, FOLLOW | WANTPARENT | AUDITVNODE1, | NDINIT_ATRIGHTS(&nd, LOOKUP, FOLLOW | WANTPARENT | AUDITVNODE1, | ||||
| pathseg, path, fd, &cap_fstat_rights); | pathseg, path, fd, &cap_fstat_rights); | ||||
| if ((error = namei(&nd)) != 0) | if ((error = namei(&nd)) != 0) | ||||
| return (error); | return (error); | ||||
| if (nd.ni_vp->v_type == VREG && nd.ni_dvp->v_type != VDIR && | |||||
| (nd.ni_vp->v_vflag & VV_ROOT) != 0) { | |||||
| struct vnode *covered_vp; | |||||
| /* | |||||
| * This happens if vp is a file mount. The call to | |||||
| * vn_fullpath_hardlink can panic if path resolution can't be | |||||
| * handled without the directory. | |||||
| * | |||||
| * To resolve this, we find the vnode which was mounted on - | |||||
| * this should have a unique global path since we disallow | |||||
| * mounting on linked files. | |||||
| */ | |||||
| error = vn_lock(nd.ni_vp, LK_SHARED); | |||||
| if (error != 0) | |||||
| goto out; | |||||
| covered_vp = nd.ni_vp->v_mount->mnt_vnodecovered; | |||||
| vref(covered_vp); | |||||
| VOP_UNLOCK(nd.ni_vp); | |||||
| error = vn_fullpath(covered_vp, &retbuf, &freebuf); | |||||
| vrele(covered_vp); | |||||
| } else { | |||||
| error = vn_fullpath_hardlink(nd.ni_vp, nd.ni_dvp, | error = vn_fullpath_hardlink(nd.ni_vp, nd.ni_dvp, | ||||
| nd.ni_cnd.cn_nameptr, nd.ni_cnd.cn_namelen, &retbuf, | nd.ni_cnd.cn_nameptr, nd.ni_cnd.cn_namelen, &retbuf, | ||||
| &freebuf, &size); | &freebuf, &size); | ||||
| } | |||||
| if (error == 0) { | if (error == 0) { | ||||
| size_t len; | size_t len; | ||||
| len = strlen(retbuf) + 1; | len = strlen(retbuf) + 1; | ||||
| if (size < len) | if (size < len) | ||||
| error = ENAMETOOLONG; | error = ENAMETOOLONG; | ||||
| else if (pathseg == UIO_USERSPACE) | else if (pathseg == UIO_USERSPACE) | ||||
| error = copyout(retbuf, buf, len); | error = copyout(retbuf, buf, len); | ||||
| else | else | ||||
| memcpy(buf, retbuf, len); | memcpy(buf, retbuf, len); | ||||
| free(freebuf, M_TEMP); | free(freebuf, M_TEMP); | ||||
| } | } | ||||
| out: | |||||
| vrele(nd.ni_vp); | vrele(nd.ni_vp); | ||||
| vrele(nd.ni_dvp); | vrele(nd.ni_dvp); | ||||
| NDFREE_PNBUF(&nd); | NDFREE_PNBUF(&nd); | ||||
| return (error); | return (error); | ||||
| } | } | ||||
| int | int | ||||
| sys___realpathat(struct thread *td, struct __realpathat_args *uap) | sys___realpathat(struct thread *td, struct __realpathat_args *uap) | ||||
| ▲ Show 20 Lines • Show All 492 Lines • ▼ Show 20 Lines | vn_fullpath_hardlink(struct vnode *vp, struct vnode *dvp, | ||||
| char **retbuf, char **freebuf, size_t *buflen) | char **retbuf, char **freebuf, size_t *buflen) | ||||
| { | { | ||||
| char *buf, *tmpbuf; | char *buf, *tmpbuf; | ||||
| struct pwd *pwd; | struct pwd *pwd; | ||||
| size_t addend; | size_t addend; | ||||
| int error; | int error; | ||||
| __enum_uint8(vtype) type; | __enum_uint8(vtype) type; | ||||
| if (*buflen < 2) | |||||
| return (EINVAL); | |||||
| if (*buflen > MAXPATHLEN) | |||||
| *buflen = MAXPATHLEN; | |||||
| buf = malloc(*buflen, M_TEMP, M_WAITOK); | |||||
| addend = 0; | |||||
| /* | /* | ||||
| * Check for VBAD to work around the vp_crossmp bug in lookup(). | * Check for VBAD to work around the vp_crossmp bug in lookup(). | ||||
| * | * | ||||
| * For example consider tmpfs on /tmp and realpath /tmp. ni_vp will be | * For example consider tmpfs on /tmp and realpath /tmp. ni_vp will be | ||||
| * set to mount point's root vnode while ni_dvp will be vp_crossmp. | * set to mount point's root vnode while ni_dvp will be vp_crossmp. | ||||
| * If the type is VDIR (like in this very case) we can skip looking | * If the type is VDIR (like in this very case) we can skip looking | ||||
| * at ni_dvp in the first place. However, since vnodes get passed here | * at ni_dvp in the first place. However, since vnodes get passed here | ||||
| * unlocked the target may transition to doomed state (type == VBAD) | * unlocked the target may transition to doomed state (type == VBAD) | ||||
| * before we get to evaluate the condition. If this happens, we will | * before we get to evaluate the condition. If this happens, we will | ||||
| * populate part of the buffer and descend to vn_fullpath_dir with | * populate part of the buffer and descend to vn_fullpath_dir with | ||||
| * vp == vp_crossmp. Prevent the problem by checking for VBAD. | * vp == vp_crossmp. Prevent the problem by checking for VBAD. | ||||
| */ | */ | ||||
| type = atomic_load_8(&vp->v_type); | type = atomic_load_8(&vp->v_type); | ||||
| if (type == VBAD) { | if (type == VBAD) | ||||
| error = ENOENT; | return (ENOENT); | ||||
| goto out_bad; | |||||
| /* | |||||
| * For file mounts dvp is vp_crossmp and unusable; resolve via the | |||||
| * covered vnode instead, whose path is unique since file mounts | |||||
| * require nlink == 1. | |||||
| */ | |||||
| if (type != VDIR && (vp->v_vflag & VV_ROOT) != 0) { | |||||
| struct vnode *covered_vp; | |||||
| size_t len; | |||||
| error = vn_lock(vp, LK_SHARED); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| /* Recheck under the lock, the unlocked test is racy. */ | |||||
kib: At least you need to recheck the condition after the vnode is locked, to see that it is really… | |||||
| if ((vp->v_vflag & VV_ROOT) == 0) { | |||||
| VOP_UNLOCK(vp); | |||||
| return (ENOENT); | |||||
| } | } | ||||
| covered_vp = vp->v_mount->mnt_vnodecovered; | |||||
| vref(covered_vp); | |||||
| VOP_UNLOCK(vp); | |||||
| error = vn_fullpath(covered_vp, retbuf, freebuf); | |||||
| vrele(covered_vp); | |||||
| if (error != 0) | |||||
| return (error); | |||||
| len = strlen(*retbuf) + 1; | |||||
| if (len > *buflen) { | |||||
| free(*freebuf, M_TEMP); | |||||
| return (ENAMETOOLONG); | |||||
| } | |||||
| *buflen = len; | |||||
| return (0); | |||||
| } | |||||
| if (*buflen < 2) | |||||
| return (EINVAL); | |||||
| if (*buflen > MAXPATHLEN) | |||||
| *buflen = MAXPATHLEN; | |||||
| buf = malloc(*buflen, M_TEMP, M_WAITOK); | |||||
| addend = 0; | |||||
| if (type != VDIR) { | if (type != VDIR) { | ||||
| addend = hrdl_name_length + 2; | addend = hrdl_name_length + 2; | ||||
| if (*buflen < addend) { | if (*buflen < addend) { | ||||
| error = ENOMEM; | error = ENOMEM; | ||||
| goto out_bad; | goto out_bad; | ||||
| } | } | ||||
| *buflen -= addend; | *buflen -= addend; | ||||
| tmpbuf = buf + *buflen; | tmpbuf = buf + *buflen; | ||||
| ▲ Show 20 Lines • Show All 2,668 Lines • Show Last 20 Lines | |||||
At least you need to recheck the condition after the vnode is locked, to see that it is really the root still.