Skip site navigation (1)Skip section navigation (2)
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>