diff -urBN /usr/src.orig/sys/fs/unionfs/union.h /usr/src/sys/fs/unionfs/union.h --- /usr/src.orig/sys/fs/unionfs/union.h 2008-11-25 15:48:49.000000000 +0900 +++ /usr/src/sys/fs/unionfs/union.h 2008-11-26 16:14:26.000000000 +0900 @@ -106,6 +106,7 @@ int unionfs_init(struct vfsconf *vfsp); int unionfs_uninit(struct vfsconf *vfsp); +int unionfs_check_crossmnt(struct vnode* lrvp, struct vnode* urvp, struct thread *td); int unionfs_nodeget(struct mount *mp, struct vnode *uppervp, struct vnode *lowervp, struct vnode *dvp, struct vnode **vpp, struct componentname *cnp, struct thread *td); void unionfs_noderem(struct vnode *vp, struct thread *td); void unionfs_get_node_status(struct unionfs_node *unp, struct thread *td, struct unionfs_node_status **unspp); diff -urBN /usr/src.orig/sys/fs/unionfs/union_subr.c /usr/src/sys/fs/unionfs/union_subr.c --- /usr/src.orig/sys/fs/unionfs/union_subr.c 2008-11-25 15:48:49.000000000 +0900 +++ /usr/src/sys/fs/unionfs/union_subr.c 2008-11-26 19:18:03.000000000 +0900 @@ -51,6 +51,7 @@ #include #include #include +#include #ifdef MAC #include @@ -66,13 +67,32 @@ MALLOC_DEFINE(M_UNIONFSNODE, "UNIONFS node", "UNIONFS vnode private part"); MALLOC_DEFINE(M_UNIONFSPATH, "UNIONFS path", "UNIONFS path private part"); +static struct mtx unionfs_mtx; + +static SYSCTL_NODE(_vfs, OID_AUTO, unionfs, CTLFLAG_RD, 0, "UNION filesystem"); + +#define LIMIT_OF_NESTLEVEL 8 +static int unionfs_max_nestlevel = 0; +static int sysctl_unionfs_max_nestlevel(SYSCTL_HANDLER_ARGS); +SYSCTL_PROC(_vfs_unionfs, OID_AUTO, max_nestlevel, CTLTYPE_INT | CTLFLAG_RW, + NULL, 0, sysctl_unionfs_max_nestlevel, "I", "maximum nest level"); + +struct ufsv { + STAILQ_ENTRY(ufsv) entries; + struct vnode *lvp; + struct vnode *uvp; + int nl; +}; + +STAILQ_HEAD(ufsvq, ufsv); + /* * Initialize */ int unionfs_init(struct vfsconf *vfsp) { - UNIONFSDEBUG("unionfs_init\n"); /* printed during system boot */ + mtx_init(&unionfs_mtx, "unionfs lock", NULL, MTX_DEF); return (0); } @@ -82,9 +102,161 @@ int unionfs_uninit(struct vfsconf *vfsp) { + mtx_destroy(&unionfs_mtx); + return (0); +} + +static int +sysctl_unionfs_max_nestlevel(SYSCTL_HANDLER_ARGS) +{ + int error; + int val; + + mtx_lock(&unionfs_mtx); + val = unionfs_max_nestlevel; + mtx_unlock(&unionfs_mtx); + + error = sysctl_handle_int(oidp, &val, 0, req); + if (error != 0 || req->newptr == NULL) + return (error); + + if (val < 0 || val > LIMIT_OF_NESTLEVEL) + return (EINVAL); + + mtx_lock(&unionfs_mtx); + unionfs_max_nestlevel = val; + mtx_unlock(&unionfs_mtx); + + return (error); +} + +static int +ufsvq_put(struct ufsvq *head, struct vnode *lvp, struct vnode *uvp, int nl) +{ + struct ufsv *np; + + np = (struct ufsv*)malloc(sizeof(struct ufsv), M_TEMP, M_NOWAIT); + if (np == NULL) + return (ENOMEM); + np->lvp = lvp; + np->uvp = uvp; + np->nl = nl +1; + STAILQ_INSERT_HEAD(head, np, entries); return (0); } +/* + * Check the cross mount. + * Cross mounts should be unnecessary. This function prevents the caller + * from cross mounting a unionfs on top of itself. + * XXX: This function can't check the complex cross mount. + * But now implementation of unionfs allows cross mount. + * So, if this function passed, it has no problem. + */ +int +unionfs_check_crossmnt(struct vnode* lrvp, struct vnode* urvp, + struct thread *td) +{ + int error; + int umnl; + int len, len1, len2; + char *rbuf1, *rbuf2; + char *fbuf1, *fbuf2; + struct vnode *lvp, *uvp; + struct ufsvq head; + struct ufsv *np, *ntmp; + + np = NULL; + STAILQ_INIT(&head); + + mtx_lock(&unionfs_mtx); + umnl = unionfs_max_nestlevel; + mtx_unlock(&unionfs_mtx); + + error = ufsvq_put(&head, lrvp, urvp, -1); + if (error != 0) + goto unionfs_check_crossmnt_out; + + while (!STAILQ_EMPTY(&head)) { + np = STAILQ_FIRST(&head); + STAILQ_REMOVE_HEAD(&head, entries); + + if (np->nl > umnl) { + printf("unionfs: The nest limit %d was exceeded.\n", + umnl); + error = (EACCES); + goto unionfs_check_crossmnt_out; + } + + error = vn_fullpath(td, np->lvp, &rbuf1, &fbuf1); + if (error != 0) + goto unionfs_check_crossmnt_out; + error = vn_fullpath(td, np->uvp, &rbuf2, &fbuf2); + if (error != 0) { + free(fbuf1, M_TEMP); + goto unionfs_check_crossmnt_out; + } + len1 = strlen(rbuf1); + len2 = strlen(rbuf2); + len = (len1 < len2 ? len1 : len2); + + if (strncmp(rbuf1, rbuf2, len) == 0) + error = (EXDEV); + + free(fbuf1, M_TEMP); + free(fbuf2, M_TEMP); + + if (error != 0) + goto unionfs_check_crossmnt_out; + + if (np->lvp->v_op == &unionfs_vnodeops) { + lvp = UNIONFSVPTOLOWERVP(np->lvp); + uvp = UNIONFSVPTOUPPERVP(np->lvp); + if (lvp != NULLVP) { + error = ufsvq_put(&head, lvp, np->uvp, np->nl); + if (error != 0) + goto unionfs_check_crossmnt_out; + } + if (uvp != NULLVP) { + error = ufsvq_put(&head, uvp, np->uvp, np->nl); + if (error != 0) + goto unionfs_check_crossmnt_out; + } + } + + if (np->uvp->v_op == &unionfs_vnodeops) { + lvp = UNIONFSVPTOLOWERVP(np->uvp); + uvp = UNIONFSVPTOUPPERVP(np->uvp); + if (lvp != NULLVP) { + error = ufsvq_put(&head, np->lvp, lvp, np->nl); + if (error != 0) + goto unionfs_check_crossmnt_out; + } + if (uvp != NULLVP) { + error = ufsvq_put(&head, np->lvp, uvp, np->nl); + if (error != 0) + goto unionfs_check_crossmnt_out; + } + } + + free(np, M_TEMP); + np = NULL; + } + +unionfs_check_crossmnt_out: + if (np != NULL) + free(np, M_TEMP); + + np = STAILQ_FIRST(&head); + while (np != NULL) { + ntmp = STAILQ_NEXT(np, entries); + free(np, M_TEMP); + np = ntmp; + } + + return (error); +} + static struct unionfs_node_hashhead * unionfs_get_hashhead(struct vnode *dvp, char *path) { diff -urBN /usr/src.orig/sys/fs/unionfs/union_vfsops.c /usr/src/sys/fs/unionfs/union_vfsops.c --- /usr/src.orig/sys/fs/unionfs/union_vfsops.c 2008-11-25 15:48:49.000000000 +0900 +++ /usr/src/sys/fs/unionfs/union_vfsops.c 2008-11-26 16:21:44.000000000 +0900 @@ -241,6 +241,15 @@ lowerrootvp = mp->mnt_vnodecovered; upperrootvp = ndp->ni_vp; + /* check cross mount */ + VOP_UNLOCK(upperrootvp, 0); + error = unionfs_check_crossmnt(lowerrootvp, upperrootvp, td); + if (error != 0) { + vrele(upperrootvp); + return (error); + } + vn_lock(upperrootvp, LK_EXCLUSIVE | LK_RETRY); + /* create unionfs_mount */ ump = (struct unionfs_mount *)malloc(sizeof(struct unionfs_mount), M_UNIONFSMNT, M_WAITOK | M_ZERO);