Date: Thu, 10 May 2001 21:01:20 +0400 (MSD) From: vova@express.ru To: FreeBSD-gnats-submit@freebsd.org Subject: kern/27250: unionfs fixes for 4.x Message-ID: <200105101701.VAA11038@vbook.express.ru>
next in thread | raw e-mail | index | archive | help
>Number: 27250 >Category: kern >Synopsis: unionfs filesystem panics in large number of situations >Confidential: no >Severity: critical >Priority: medium >Responsible: freebsd-bugs >State: open >Quarter: >Keywords: >Date-Required: >Class: sw-bug >Submitter-Id: current-users >Arrival-Date: Thu May 10 10:10:01 PDT 2001 >Closed-Date: >Last-Modified: >Originator: Vladimir B. Grebenschikov >Release: FreeBSD 4.2-STABLE i386 >Organization: TSB "Russian Express" >Environment: It was tested on 4.1.1-RELEASE, 4.2-RELEASE, 4.3-RELEASE Mount some part of filesystem with mount_union >Description: 1. sometimes using mmap'ed files over mount union cause panic 2. possible panic when work with sockets/FIFO/dev's on unionfs 3. panic when no enough rights on current directory when compiled with DIAGNOSTIC 4. number of vnode leaks 5. security hole in readdir() - it is possible to go out of the chroot() using readdir on unionfs (getdirentries+getdirentries+fchdir) >How-To-Repeat: 1. To repeat try to build staroffice port (/usr/ports/editors/staroffice52/) on unionfs. 2. try to use AF_UNIX sockets over unionfs 3. build unionfs wiht DIAGNOSTIC and try to do something in directory where you have no rights to write 4. busy software on unionfs with load and observe vnode leak in systat -vm (numvnodes) 5. when doing getdirentries on unionfs directory it first lists upper layer, then lower layer, beetween reading vnode of directory changd from upper to lower layer, so just after second getdirentries it is possible to do fchdir() on this descriptor to out of chroot(). >Fix: Patch was tested on 4.1.1-RELEASE, 4.2-RELEASE, 4.3-RELEASE Index: src/sys/kern/vfs_syscalls.c =================================================================== RCS file: /home1/cvsroot//freebsd/src/sys/kern/vfs_syscalls.c,v retrieving revision 1.151.2.5.2.3 diff -u -r1.151.2.5.2.3 vfs_syscalls.c --- src/sys/kern/vfs_syscalls.c 2001/02/27 13:25:48 1.151.2.5.2.3 +++ src/sys/kern/vfs_syscalls.c 2001/05/04 07:35:07 @@ -81,7 +81,9 @@ const struct timespec *, int)); static int usermount = 0; /* if 1, non-root can mount fs. */ +/* int (*union_dircheckp) __P((struct proc *, struct vnode **, struct file *)); +*/ SYSCTL_INT(_vfs, OID_AUTO, usermount, CTLFLAG_RW, &usermount, 0, ""); @@ -2854,7 +2859,7 @@ if ((fp->f_flag & FREAD) == 0) return (EBADF); vp = (struct vnode *)fp->f_data; -unionread: +/*unionread:*/ if (vp->v_type != VDIR) return (EINVAL); aiov.iov_base = SCARG(uap, buf); @@ -2921,6 +2926,7 @@ VOP_UNLOCK(vp, 0, p); if (error) return (error); +/* if (SCARG(uap, count) == auio.uio_resid) { if (union_dircheckp) { error = union_dircheckp(p, &vp, fp); @@ -2940,6 +2946,7 @@ goto unionread; } } +*/ error = copyout((caddr_t)&loff, (caddr_t)SCARG(uap, basep), sizeof(long)); p->p_retval[0] = SCARG(uap, count) - auio.uio_resid; @@ -2980,7 +2987,7 @@ if ((fp->f_flag & FREAD) == 0) return (EBADF); vp = (struct vnode *)fp->f_data; -unionread: +/*unionread:*/ if (vp->v_type != VDIR) return (EINVAL); aiov.iov_base = SCARG(uap, buf); @@ -2999,6 +3006,7 @@ VOP_UNLOCK(vp, 0, p); if (error) return (error); +/* if (SCARG(uap, count) == auio.uio_resid) { if (union_dircheckp) { error = union_dircheckp(p, &vp, fp); @@ -3018,6 +3026,7 @@ goto unionread; } } +*/ if (SCARG(uap, basep) != NULL) { error = copyout((caddr_t)&loff, (caddr_t)SCARG(uap, basep), sizeof(long)); Index: src/sys/vm/vm_mmap.c =================================================================== RCS file: /home1/cvsroot//freebsd/src/sys/vm/vm_mmap.c,v retrieving revision 1.108.2.2.2.1 diff -u -r1.108.2.2.2.1 vm_mmap.c --- src/sys/vm/vm_mmap.c 2000/11/03 00:37:39 1.108.2.2.2.1 +++ src/sys/vm/vm_mmap.c 2001/05/04 07:35:07 @@ -78,6 +78,8 @@ #include <vm/vm_page.h> #include <vm/vm_kern.h> +#include <miscfs/union/union.h> + #ifndef _SYS_SYSPROTO_H_ struct sbrk_args { int incr; @@ -1074,6 +1076,15 @@ /* * Lookup/allocate object. */ + vp = (struct vnode *) handle; +#if 1 + if( vp ) { + while( vp->v_tag == VT_UNION) { + vp = OTHERVP(vp); + } + handle = (void *)vp; + } +#endif if (flags & MAP_ANON) { type = OBJT_DEFAULT; /* @@ -1082,7 +1093,7 @@ if (handle == 0) foff = 0; } else { - vp = (struct vnode *) handle; +/* vp = (struct vnode *) handle; */ if (vp->v_type == VCHR) { type = OBJT_DEVICE; handle = (void *)(intptr_t)vp->v_rdev; Index: src/sys/miscfs/union/union.h =================================================================== RCS file: /home1/cvsroot//freebsd/src/sys/miscfs/union/union.h,v retrieving revision 1.17 diff -u -r1.17 union.h --- src/sys/miscfs/union/union.h 1999/12/29 04:54:48 1.17 +++ src/sys/miscfs/union/union.h 2001/05/04 07:35:07 @@ -97,6 +103,9 @@ #endif }; +#define DOCACHE 1 /*union_allocvp argument: default "1"*/ + +#define UPPERLOCK 0 /* Need uppervp to lock */ /* * XXX UN_ULOCK - indicates that the uppervp is locked * Index: src/sys/miscfs/union/union_subr.c =================================================================== RCS file: /home1/cvsroot//freebsd/src/sys/miscfs/union/union_subr.c,v retrieving revision 1.43 diff -u -r1.43 union_subr.c --- src/sys/miscfs/union/union_subr.c 1999/12/15 23:02:12 1.43 +++ src/sys/miscfs/union/union_subr.c 2001/05/04 07:35:07 @@ -343,7 +344,9 @@ { int error; struct union_node *un = 0; +/* struct vnode *xlowervp = NULLVP; +*/ struct union_mount *um = MOUNTTOUNIONMOUNT(mp); struct proc *p = (cnp) ? cnp->cn_proc : curproc; int hash = 0; @@ -354,7 +357,10 @@ panic("union: unidentifiable allocation"); if (uppervp && lowervp && (uppervp->v_type != lowervp->v_type)) { +/* xlowervp = lowervp; +*/ + vrele(lowervp); lowervp = NULLVP; } @@ -597,8 +603,10 @@ } out: +/* if (xlowervp) vrele(xlowervp); +*/ if (docache) union_list_unlock(hash); @@ -1304,6 +1324,7 @@ /* * Module glue to remove #ifdef UNION from vfs_syscalls.c */ +#ifdef NEED_BAD_HACK static int union_dircheck(struct proc *p, struct vnode **vp, struct file *fp) { @@ -1312,6 +1333,9 @@ if ((*vp)->v_op == union_vnodeop_p) { struct vnode *lvp; + if(LOWERVP(*vp) == NULL) { + return 0; + } lvp = union_dircache(*vp, p); if (lvp != NULLVP) { struct vattr va; @@ -1347,13 +1371,15 @@ } return error; } +#endif +#ifdef NEED_BAD_HACK static int union_modevent(module_t mod, int type, void *data) { switch (type) { case MOD_LOAD: - union_dircheckp = union_dircheck; + union_dircheckp = union_dircheck; break; case MOD_UNLOAD: union_dircheckp = NULL; @@ -1371,3 +1397,4 @@ }; DECLARE_MODULE(union_dircheck, union_mod, SI_SUB_VFS, SI_ORDER_ANY); +#endif Index: src/sys/miscfs/union/union_vfsops.c =================================================================== RCS file: /home1/cvsroot//freebsd/src/sys/miscfs/union/union_vfsops.c,v retrieving revision 1.39 diff -u -r1.39 union_vfsops.c --- src/sys/miscfs/union/union_vfsops.c 1999/12/19 06:07:52 1.39 +++ src/sys/miscfs/union/union_vfsops.c 2001/05/04 07:35:07 @@ -407,7 +412,7 @@ VREF(um->um_lowervp); error = union_allocvp(vpp, mp, NULLVP, NULLVP, NULL, - um->um_uppervp, um->um_lowervp, 1); + um->um_uppervp, um->um_lowervp, DOCACHE); UDEBUG(("error %d\n", error)); UDEBUG(("union_root2 UPPERVP %p locked = %d\n", um->um_uppervp, VOP_ISLOCKED(um->um_uppervp, NULL))); @@ -415,6 +420,22 @@ return (error); } +static void union_statfs_copy( struct statfs *sbp, struct statfs *mstat, struct statfs *main ) { + sbp->f_bsize = mstat->f_bsize; + sbp->f_blocks = mstat->f_blocks; + sbp->f_bfree = mstat->f_bfree; + sbp->f_bavail = mstat->f_bavail; + sbp->f_files = mstat->f_files; + sbp->f_ffree = mstat->f_ffree; + sbp->f_flags = mstat->f_flags; + sbp->f_iosize = mstat->f_iosize; + if (sbp != main) { + sbp->f_type = main->f_type; + bcopy(&main->f_fsid, &sbp->f_fsid, sizeof(sbp->f_fsid)); + bcopy(&main->f_mntonname, sbp->f_mntonname, MNAMELEN); + bcopy(&main->f_mntfromname, sbp->f_mntfromname, MNAMELEN); + } +} static int union_statfs(mp, sbp, p) struct mount *mp; @@ -432,9 +453,22 @@ bzero(&mstat, sizeof(mstat)); if (um->um_lowervp) { + if( um->um_lowervp->v_mount == um->um_uppervp->v_mount ) { + error = VFS_STATFS(um->um_uppervp->v_mount, &mstat, p); + union_statfs_copy(sbp, &mstat, &mp->mnt_stat); + return error; + } error = VFS_STATFS(um->um_lowervp->v_mount, &mstat, p); - if (error) - return (error); +/*uprintf("Lowrvp fsstat return %d\n", error);*/ + if (error) { + error = VFS_STATFS(um->um_uppervp->v_mount, &mstat, p); + union_statfs_copy(sbp, &mstat, &mp->mnt_stat); + return error; + } + } else { + error = VFS_STATFS(um->um_uppervp->v_mount, &mstat, p); + union_statfs_copy(sbp, &mstat, &mp->mnt_stat); + return error; } /* now copy across the "interesting" information and fake the rest */ @@ -452,6 +486,7 @@ sbp->f_ffree = mstat.f_ffree; error = VFS_STATFS(um->um_uppervp->v_mount, &mstat, p); +/*uprintf("Uppervp fsstat return %d\n", error); */ if (error) return (error); Index: src/sys/miscfs/union/union_vnops.c =================================================================== RCS file: /home1/cvsroot//freebsd/src/sys/miscfs/union/union_vnops.c,v retrieving revision 1.72 diff -u -r1.72 union_vnops.c --- src/sys/miscfs/union/union_vnops.c 1999/12/15 23:02:14 1.72 +++ src/sys/miscfs/union/union_vnops.c 2001/05/04 07:35:07 @@ -275,6 +275,8 @@ return (0); } +#define NOOPEN_LOWER 0 + static int union_lookup(ap) struct vop_lookup_args /* { @@ -300,6 +302,9 @@ *ap->a_vpp = NULLVP; +/*if ((cnp->cn_nameiop == DELETE || cnp->cn_nameiop == RENAME)) { +printf("lookup: [%.*s] for %d\ncnp->cn_consume = %d\n",cnp->cn_namelen,cnp->cn_nameptr,cnp->cn_nameiop,cnp->cn_consume); +}*/ /* * Disallow write attemps to the filesystem mounted read-only. */ @@ -367,7 +372,12 @@ (uppervp ? uppervp->v_usecount : -99), (uppervp ? VOP_ISLOCKED(uppervp, NULL) : -99) )); - +/* + if(uerror == EACCES) { + error = uerror; + goto out; + } +*/ /* * Disallow write attemps to the filesystem mounted read-only. */ @@ -538,9 +548,52 @@ VOP_UNLOCK(lowervp, 0, p); if (upperdvp) VOP_UNLOCK(upperdvp, 0, p); - - error = union_allocvp(ap->a_vpp, dvp->v_mount, dvp, upperdvp, cnp, - uppervp, lowervp, 1); +/* +printf("lookup_end: [%.*s] for %d\n",cnp->cn_namelen,cnp->cn_nameptr,cnp->cn_nameiop); +*/ + { + struct vnode *tmp; + tmp = (uppervp != NULL) ? uppervp : lowervp; +/* +printf("vnode = %p\ncnp->cn_nameiop = %d, cnp->cn_name = %.*s, type = %d\nv_usecount = %d\n", + tmp, + cnp->cn_nameiop, cnp->cn_namelen, cnp->cn_nameptr, tmp->v_type, + tmp->v_usecount); +*/ + if( (cnp->cn_nameiop == LOOKUP && + ( +#if NOOPEN_LOWER + tmp->v_type != VREG && +#endif + tmp->v_type != VDIR && + tmp->v_type != VLNK) + ) +#if NOOPEN_LOWER + || + (tmp->v_type == VREG && (uppervp != NULL) && ( + cnp->cn_nameiop == LOOKUP || cnp->cn_nameiop == CREATE)) +#endif + ){ + *(ap->a_vpp) = tmp; + if(uppervp && lowervp) { + vput(lowervp); + lowervp = 0; + } + if (upperdvp) { + if (upperdvp == tmp) { + vrele(upperdvp); + } else { + vput(upperdvp); + } + upperdvp = NULL; + } + error = 0; + }else{ + error = union_allocvp(ap->a_vpp, dvp->v_mount, + dvp, upperdvp, cnp, + uppervp, lowervp, 1); + } + } UDEBUG(("Create %p = %p %p refs=%d\n", *ap->a_vpp, uppervp, lowervp, (*ap->a_vpp) ? ((*ap->a_vpp)->v_usecount) : -99)); @@ -612,7 +665,8 @@ if (cnp->cn_namelen == 1 && cnp->cn_nameptr[0] == '.' && *ap->a_vpp != dvp) { - panic("union_lookup returning . (%p) not same as startdir (%p)", ap->a_vpp, dvp); + /*panic("union_lookup returning . (%p) not same as startdir (%p)", *ap->a_vpp, dvp);*/ + printf("union_lookup returning . (%p) not same as startdir (%p). error = %d\n", *ap->a_vpp, dvp, error); } #endif @@ -642,6 +696,9 @@ int error = EROFS; if ((dvp = union_lock_upper(dun, p)) != NULL) { +#if 1 + error = VOP_CREATE(dvp, ap->a_vpp, cnp, ap->a_vap); +#else struct vnode *vp; struct mount *mp; @@ -654,6 +711,7 @@ cnp, vp, NULLVP, 1); UDEBUG(("ALLOCVP-2B FROM %p REFS %d\n", *ap->a_vpp, vp->v_usecount)); } +#endif union_unlock_upper(dvp, p); } return (error); @@ -672,6 +730,10 @@ struct vnode *uppervp; int error = EOPNOTSUPP; + if (cnp == NULL) { + return 0; + } + if ((uppervp = union_lock_upper(un, cnp->cn_proc)) != NULLVP) { error = VOP_WHITEOUT(un->un_uppervp, cnp, ap->a_flags); union_unlock_upper(uppervp, cnp->cn_proc); @@ -1101,8 +1163,10 @@ struct vnode *uppervp; int error; - if ((uppervp = union_lock_upper(un, p)) == NULLVP) + if ((uppervp = union_lock_upper(un, p)) == NULLVP) { + printf("un_uppervp: %x\n", un->un_uppervp); panic("union: missing upper layer in write"); + } /* * Since our VM pages are associated with our vnode rather then @@ -1318,7 +1382,7 @@ if (tun->un_uppervp == NULLVP) { vn_lock(ap->a_vp, LK_EXCLUSIVE | LK_RETRY, p); -#if 0 +#if UPPERLOCK if (dun->un_uppervp == tun->un_dirvp) { if (dun->un_flags & UN_ULOCK) { dun->un_flags &= ~UN_ULOCK; @@ -1327,7 +1391,7 @@ } #endif error = union_copyup(tun, 1, cnp->cn_cred, p); -#if 0 +#if UPPERLOCK if (dun->un_uppervp == tun->un_dirvp) { vn_lock(dun->un_uppervp, LK_EXCLUSIVE | LK_RETRY, p); @@ -1647,12 +1711,35 @@ struct proc *p = ap->a_uio->uio_procp; struct vnode *uvp; int error = 0; + int save_resid; - if ((uvp = union_lock_upper(un, p)) != NULLVP) { + if ((un->un_uppersz == VNOVAL || un->un_uppersz >= ap->a_uio->uio_offset) && (uvp = union_lock_upper(un, p)) != NULLVP) { +/*uprintf("readdir un->un_uppersz = %d ap->a_uio->uio_offset = %d\n", un->un_uppersz, ap->a_uio->uio_offset);*/ ap->a_vp = uvp; + save_resid = ap->a_uio->uio_resid; error = VCALL(uvp, VOFFSET(vop_readdir), ap); + if(error) { + return(error); + } union_unlock_upper(uvp, p); + if(un->un_uppersz == VNOVAL || ap->a_uio->uio_offset > un->un_uppersz) { + un->un_uppersz = ap->a_uio->uio_offset; + } + if(ap->a_uio->uio_resid == save_resid) { + un->un_uppersz = ap->a_uio->uio_offset; + } else { + return(0); + } } + if ((uvp = un->un_lowervp)) { + ap->a_uio->uio_offset -= un->un_uppersz; + ap->a_vp = uvp; + VREF(uvp); + vn_lock(uvp, LK_EXCLUSIVE | LK_CANRECURSE | LK_RETRY, p); + error = VCALL(uvp, VOFFSET(vop_readdir), ap); + vput(uvp); + ap->a_uio->uio_offset += un->un_uppersz; + } return(error); } @@ -1718,7 +1805,7 @@ un->un_dircache = 0; } -#if 0 +#if UPPERLOCK if ((un->un_flags & UN_ULOCK) && un->un_uppervp) { un->un_flags &= ~UN_ULOCK; VOP_UNLOCK(un->un_uppervp, 0, p); @@ -1748,7 +1835,7 @@ union_lock(ap) struct vop_lock_args *ap; { -#if 0 +#if UPPERLOCK struct vnode *vp = ap->a_vp; struct proc *p = ap->a_p; int flags = ap->a_flags; @@ -1757,7 +1844,7 @@ int error; error = vop_stdlock(ap); -#if 0 +#if UPPERLOCK un = VTOUNION(vp); if (error == 0) { @@ -1800,11 +1887,14 @@ { struct union_node *un = VTOUNION(ap->a_vp); int error; +#if UPPERLOCK + struct proc *p = ap->a_p; +#endif KASSERT((un->un_uppervp == NULL || un->un_uppervp->v_usecount > 0), ("uppervp usecount is 0")); error = vop_stdunlock(ap); -#if 0 +#if UPPERLOCK /* * If no exclusive locks remain and we are holding an uppervp lock, >Release-Note: >Audit-Trail: >Unformatted: To Unsubscribe: send mail to majordomo@FreeBSD.org with "unsubscribe freebsd-bugs" in the body of the message
Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?200105101701.VAA11038>