From f4a876ac4ba08b4040c8e2561f446accdf8f62c6 Mon Sep 17 00:00:00 2001 From: mm Date: Tue, 14 Feb 2012 17:09:20 +0000 Subject: [PATCH 078/175] MFC r230129,r230143,r230407,r231012: MFC r320129 [1]: Introduce vn_path_to_global_path() This function updates path string to vnode's full global path and checks the size of the new path string against the pathlen argument. In vfs_domount(), sys_unmount() and kern_jail_set() this new function is used to update the supplied path argument to the respective global path. Unbreaks jailed zfs(8) with enforce_statfs set to 1. MFC r230143 [2]: Fix missing in r230129: kern_jail.c: initialize fullpath_disabled to zero vfs_cache.c: add missing dot in comment MFC r230407 [3]: Use separate buffer for global path to avoid overflow of path buffer. MFC r231012: Analogous to r230407 a separate path buffer in vfs_mount.c is required for r230129. Fixes a out of bounds write to fspath. Reviewed by: kib [1] [2], jamie [3] git-svn-id: http://svn.freebsd.org/base/stable/9@231692 ccf9f872-aa2e-dd11-9fc8-001c23d0bc1f (cherry picked from commit 32b121f5912282938620155f6dea0567f5b571b9) Signed-off-by: Xin Li --- sys/kern/kern_jail.c | 59 +++++++++++++++++++++++++++------------- sys/kern/vfs_cache.c | 74 ++++++++++++++++++++++++++++++++++++++++++++++++++ sys/kern/vfs_mount.c | 33 ++++++++++++++++++---- sys/sys/vnode.h | 2 ++ 4 files changed, 144 insertions(+), 24 deletions(-) diff --git a/sys/kern/kern_jail.c b/sys/kern/kern_jail.c index 37a2c59..b5b0000 100644 --- a/sys/kern/kern_jail.c +++ b/sys/kern/kern_jail.c @@ -521,6 +521,7 @@ kern_jail_set(struct thread *td, struct uio *optuio, int flags) 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 *optuio, int flags) 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 *optuio, int flags) #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 *optuio, int flags) } #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 *optuio, int flags) 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 *optuio, int flags) } 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 *optuio, int flags) #ifdef INET6 free(ip6, M_PRISON); #endif + if (g_path != NULL) + free(g_path, M_TEMP); vfs_freeopts(opts); return (error); } diff --git a/sys/kern/vfs_cache.c b/sys/kern/vfs_cache.c index 7daef57..461e22b 100644 --- a/sys/kern/vfs_cache.c +++ b/sys/kern/vfs_cache.c @@ -44,6 +44,7 @@ __FBSDID("$FreeBSD$"); #include #include #include +#include #include #include #include @@ -1277,3 +1278,76 @@ vn_commname(struct vnode *vp, char *buf, u_int buflen) 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); +} diff --git a/sys/kern/vfs_mount.c b/sys/kern/vfs_mount.c index e8b89e5..41a6411 100644 --- a/sys/kern/vfs_mount.c +++ b/sys/kern/vfs_mount.c @@ -1007,6 +1007,7 @@ vfs_domount( struct vfsconf *vfsp; struct nameidata nd; struct vnode *vp; + char *pathbuf; int error; /* @@ -1070,11 +1071,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__); @@ -1104,9 +1111,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) { @@ -1140,6 +1148,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) diff --git a/sys/sys/vnode.h b/sys/sys/vnode.h index 4cb6633..fcf4a91 100644 --- a/sys/sys/vnode.h +++ b/sys/sys/vnode.h @@ -604,6 +604,8 @@ int vn_fullpath(struct thread *td, struct vnode *vn, 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); -- 1.7.9.4