Index: usr.sbin/jail/jail.c =================================================================== --- usr.sbin/jail/jail.c (revision 230996) +++ usr.sbin/jail/jail.c (working copy) @@ -508,6 +508,7 @@ static void set_param(const char *name, char *value) { struct jailparam *param; + char path[PATH_MAX]; int i; static int paramlistsize; @@ -520,8 +521,13 @@ set_param(const char *name, char *value) } /* jail_set won't chdir along with its chroot, so do it here. */ - if (!strcmp(name, "path") && chdir(value) < 0) - err(1, "chdir: %s", value); + if (!strcmp(name, "path")) { + /* resolve the path with realpath(3) */ + if (realpath(value, path) != NULL) + value = path; + if (chdir(value) < 0) + err(1, "chdir: %s", value); + } /* Check for repeat parameters */ for (i = 0; i < nparams; i++) Index: sys/fs/pseudofs/pseudofs.h =================================================================== --- sys/fs/pseudofs/pseudofs.h (revision 230996) +++ sys/fs/pseudofs/pseudofs.h (working copy) @@ -303,7 +303,7 @@ static struct vfsops name##_vfsops = { \ .vfs_uninit = _##name##_uninit, \ .vfs_unmount = pfs_unmount, \ }; \ -VFS_SET(name##_vfsops, name, VFCF_SYNTHETIC); \ +VFS_SET(name##_vfsops, name, VFCF_SYNTHETIC | VFCF_JAIL); \ MODULE_VERSION(name, version); \ MODULE_DEPEND(name, pseudofs, 1, 1, 1); Index: sys/fs/nullfs/null_vfsops.c =================================================================== --- sys/fs/nullfs/null_vfsops.c (revision 230996) +++ sys/fs/nullfs/null_vfsops.c (working copy) @@ -357,4 +357,4 @@ static struct vfsops null_vfsops = { .vfs_vget = nullfs_vget, }; -VFS_SET(null_vfsops, nullfs, VFCF_LOOPBACK); +VFS_SET(null_vfsops, nullfs, VFCF_LOOPBACK | VFCF_JAIL); Index: sys/fs/devfs/devfs_vfsops.c =================================================================== --- sys/fs/devfs/devfs_vfsops.c (revision 230996) +++ sys/fs/devfs/devfs_vfsops.c (working copy) @@ -186,4 +186,4 @@ static struct vfsops devfs_vfsops = { .vfs_unmount = devfs_unmount, }; -VFS_SET(devfs_vfsops, devfs, VFCF_SYNTHETIC); +VFS_SET(devfs_vfsops, devfs, VFCF_SYNTHETIC | VFCF_JAIL); Index: sys/sys/vnode.h =================================================================== --- sys/sys/vnode.h (revision 230996) +++ sys/sys/vnode.h (working copy) @@ -605,6 +605,8 @@ int vn_fullpath(struct thread *td, struct vnode *v int vn_fullpath_global(struct thread *td, struct vnode *vn, char **retbuf, char **freebuf); int vn_commname(struct vnode *vn, char *buf, u_int buflen); +int vn_path_to_global_path(struct thread *td, struct vnode *vp, + char *path, u_int pathlen); int vaccess(enum vtype type, mode_t file_mode, uid_t file_uid, gid_t file_gid, accmode_t accmode, struct ucred *cred, int *privused); Index: sys/kern/kern_jail.c =================================================================== --- sys/kern/kern_jail.c (revision 230996) +++ sys/kern/kern_jail.c (working copy) @@ -521,6 +521,7 @@ kern_jail_set(struct thread *td, struct uio *optui struct prison *pr, *deadpr, *mypr, *ppr, *tpr; struct vnode *root; char *domain, *errmsg, *host, *name, *namelc, *p, *path, *uuid; + char *g_path; #if defined(INET) || defined(INET6) struct prison *tppr; void *op; @@ -531,6 +532,7 @@ kern_jail_set(struct thread *td, struct uio *optui int gotchildmax, gotenforce, gothid, gotslevel; int fi, jid, jsys, len, level; int childmax, slevel, vfslocked; + int fullpath_disabled; #if defined(INET) || defined(INET6) int ii, ij; #endif @@ -574,6 +576,7 @@ kern_jail_set(struct thread *td, struct uio *optui #ifdef INET6 ip6 = NULL; #endif + g_path = NULL; error = vfs_copyopt(opts, "jid", &jid, sizeof(jid)); if (error == ENOENT) @@ -880,6 +883,7 @@ kern_jail_set(struct thread *td, struct uio *optui } #endif + fullpath_disabled = 0; root = NULL; error = vfs_getopt(opts, "path", (void **)&path, &len); if (error == ENOENT) @@ -897,30 +901,44 @@ kern_jail_set(struct thread *td, struct uio *optui error = EINVAL; goto done_free; } - if (len < 2 || (len == 2 && path[0] == '/')) - path = NULL; - else { + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | MPSAFE, UIO_SYSSPACE, + path, td); + error = namei(&nd); + if (error) + goto done_free; + vfslocked = NDHASGIANT(&nd); + root = nd.ni_vp; + NDFREE(&nd, NDF_ONLY_PNBUF); + g_path = malloc(MAXPATHLEN, M_TEMP, M_WAITOK); + strlcpy(g_path, path, MAXPATHLEN); + error = vn_path_to_global_path(td, root, g_path, MAXPATHLEN); + if (error == 0) + path = g_path; + else if (error == ENODEV) { + /* proceed if sysctl debug.disablefullpath == 1 */ + fullpath_disabled = 1; + if (len < 2 || (len == 2 && path[0] == '/')) + path = NULL; + } else { + /* exit on other errors */ + VFS_UNLOCK_GIANT(vfslocked); + goto done_free; + } + if (root->v_type != VDIR) { + error = ENOTDIR; + vput(root); + VFS_UNLOCK_GIANT(vfslocked); + goto done_free; + } + VOP_UNLOCK(root, 0); + VFS_UNLOCK_GIANT(vfslocked); + if (fullpath_disabled) { /* Leave room for a real-root full pathname. */ if (len + (path[0] == '/' && strcmp(mypr->pr_path, "/") ? strlen(mypr->pr_path) : 0) > MAXPATHLEN) { error = ENAMETOOLONG; goto done_free; } - NDINIT(&nd, LOOKUP, MPSAFE | FOLLOW, UIO_SYSSPACE, - path, td); - error = namei(&nd); - if (error) - goto done_free; - vfslocked = NDHASGIANT(&nd); - root = nd.ni_vp; - NDFREE(&nd, NDF_ONLY_PNBUF); - if (root->v_type != VDIR) { - error = ENOTDIR; - vrele(root); - VFS_UNLOCK_GIANT(vfslocked); - goto done_free; - } - VFS_UNLOCK_GIANT(vfslocked); } } @@ -1583,7 +1601,8 @@ kern_jail_set(struct thread *td, struct uio *optui } if (path != NULL) { /* Try to keep a real-rooted full pathname. */ - if (path[0] == '/' && strcmp(mypr->pr_path, "/")) + if (fullpath_disabled && path[0] == '/' && + strcmp(mypr->pr_path, "/")) snprintf(pr->pr_path, sizeof(pr->pr_path), "%s%s", mypr->pr_path, path); else @@ -1806,6 +1825,8 @@ kern_jail_set(struct thread *td, struct uio *optui #ifdef INET6 free(ip6, M_PRISON); #endif + if (g_path != NULL) + free(g_path, M_TEMP); vfs_freeopts(opts); return (error); } Index: sys/kern/vfs_mount.c =================================================================== --- sys/kern/vfs_mount.c (revision 230996) +++ sys/kern/vfs_mount.c (working copy) @@ -1024,6 +1024,7 @@ vfs_domount( struct vfsconf *vfsp; struct nameidata nd; struct vnode *vp; + char *pathbuf; int error; /* @@ -1087,11 +1088,17 @@ vfs_domount( NDFREE(&nd, NDF_ONLY_PNBUF); vp = nd.ni_vp; if ((fsflags & MNT_UPDATE) == 0) { - error = vfs_domount_first(td, vfsp, fspath, vp, fsflags, - optlist); - } else { + pathbuf = malloc(MNAMELEN, M_TEMP, M_WAITOK); + strcpy(pathbuf, fspath); + error = vn_path_to_global_path(td, vp, pathbuf, MNAMELEN); + /* debug.disablefullpath == 1 results in ENODEV */ + if (error == 0 || error == ENODEV) { + error = vfs_domount_first(td, vfsp, pathbuf, vp, + fsflags, optlist); + } + free(pathbuf, M_TEMP); + } else error = vfs_domount_update(td, vp, fsflags, optlist); - } mtx_unlock(&Giant); ASSERT_VI_UNLOCKED(vp, __func__); @@ -1121,9 +1128,10 @@ sys_unmount(td, uap) int flags; } */ *uap; { + struct nameidata nd; struct mount *mp; char *pathbuf; - int error, id0, id1; + int error, id0, id1, vfslocked; AUDIT_ARG_VALUE(uap->flags); if (jailed(td->td_ucred) || usermount == 0) { @@ -1157,6 +1165,21 @@ sys_unmount(td, uap) mtx_unlock(&mountlist_mtx); } else { AUDIT_ARG_UPATH1(td, pathbuf); + /* + * Try to find global path for path argument. + */ + NDINIT(&nd, LOOKUP, + FOLLOW | LOCKLEAF | MPSAFE | AUDITVNODE1, + UIO_SYSSPACE, pathbuf, td); + if (namei(&nd) == 0) { + vfslocked = NDHASGIANT(&nd); + NDFREE(&nd, NDF_ONLY_PNBUF); + error = vn_path_to_global_path(td, nd.ni_vp, pathbuf, + MNAMELEN); + if (error == 0 || error == ENODEV) + vput(nd.ni_vp); + VFS_UNLOCK_GIANT(vfslocked); + } mtx_lock(&mountlist_mtx); TAILQ_FOREACH_REVERSE(mp, &mountlist, mntlist, mnt_list) { if (strcmp(mp->mnt_stat.f_mntonname, pathbuf) == 0) Index: sys/kern/vfs_cache.c =================================================================== --- sys/kern/vfs_cache.c (revision 230996) +++ sys/kern/vfs_cache.c (working copy) @@ -45,6 +45,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -1277,3 +1278,76 @@ vn_commname(struct vnode *vp, char *buf, u_int buf buf[l] = '\0'; return (0); } + +/* + * This function updates path string to vnode's full global path + * and checks the size of the new path string against the pathlen argument. + * + * Requires a locked, referenced vnode and GIANT lock held. + * Vnode is re-locked on success or ENODEV, otherwise unlocked. + * + * If sysctl debug.disablefullpath is set, ENODEV is returned, + * vnode is left locked and path remain untouched. + * + * If vp is a directory, the call to vn_fullpath_global() always succeeds + * because it falls back to the ".." lookup if the namecache lookup fails. + */ +int +vn_path_to_global_path(struct thread *td, struct vnode *vp, char *path, + u_int pathlen) +{ + struct nameidata nd; + struct vnode *vp1; + char *rpath, *fbuf; + int error, vfslocked; + + VFS_ASSERT_GIANT(vp->v_mount); + ASSERT_VOP_ELOCKED(vp, __func__); + + /* Return ENODEV if sysctl debug.disablefullpath==1 */ + if (disablefullpath) + return (ENODEV); + + /* Construct global filesystem path from vp. */ + VOP_UNLOCK(vp, 0); + error = vn_fullpath_global(td, vp, &rpath, &fbuf); + + if (error != 0) { + vrele(vp); + return (error); + } + + if (strlen(rpath) >= pathlen) { + vrele(vp); + error = ENAMETOOLONG; + goto out; + } + + /* + * Re-lookup the vnode by path to detect a possible rename. + * As a side effect, the vnode is relocked. + * If vnode was renamed, return ENOENT. + */ + NDINIT(&nd, LOOKUP, FOLLOW | LOCKLEAF | MPSAFE | AUDITVNODE1, + UIO_SYSSPACE, path, td); + error = namei(&nd); + if (error != 0) { + vrele(vp); + goto out; + } + vfslocked = NDHASGIANT(&nd); + NDFREE(&nd, NDF_ONLY_PNBUF); + vp1 = nd.ni_vp; + vrele(vp); + if (vp1 == vp) + strcpy(path, rpath); + else { + vput(vp1); + error = ENOENT; + } + VFS_UNLOCK_GIANT(vfslocked); + +out: + free(fbuf, M_TEMP); + return (error); +}