Save lower root vnode in nullfs mnt data instead of upper.
Nullfs needs to know the root vnode of the lower fs during the operation. Currently it caches the upper vnode of it, which is also the root of the nullfs mount. On unmount, nullfs calls vflush() with rootrefs == 1, and aborts non-forced unmount if there are any more vnodes instantiated during vflush(). This means that the reference to the root vnode after failed non-forced unmount could be lost and nullm_rootvp points to the freed memory. Fix it by storing the reference for lower vnode instead, which is kept intact during vflush(). nullfs_root() now instantiates the upper vnode of lower root. Care about VV_ROOT flag in null_nodeget(). Reported and tested by: pho Sponsored by: The FreeBSD Foundation MFC after: 1 week
This commit is contained in:
parent
e1e9560efa
commit
8267ddac8e
@ -43,7 +43,7 @@
|
||||
|
||||
struct null_mount {
|
||||
struct mount *nullm_vfs;
|
||||
struct vnode *nullm_rootvp; /* Reference to root null_node */
|
||||
struct vnode *nullm_lowerrootvp; /* Ref to lower root vnode */
|
||||
uint64_t nullm_flags;
|
||||
};
|
||||
|
||||
|
@ -252,6 +252,8 @@ null_nodeget(mp, lowervp, vpp)
|
||||
vp->v_type = lowervp->v_type;
|
||||
vp->v_data = xp;
|
||||
vp->v_vnlock = lowervp->v_vnlock;
|
||||
if (lowervp == MOUNTTONULLMOUNT(mp)->nullm_lowerrootvp)
|
||||
vp->v_vflag |= VV_ROOT;
|
||||
error = insmntque1(vp, mp, null_insmntque_dtr, xp);
|
||||
if (error != 0)
|
||||
return (error);
|
||||
|
@ -74,7 +74,7 @@ static vfs_extattrctl_t nullfs_extattrctl;
|
||||
static int
|
||||
nullfs_mount(struct mount *mp)
|
||||
{
|
||||
struct vnode *lowerrootvp, *vp;
|
||||
struct vnode *lowerrootvp;
|
||||
struct vnode *nullm_rootvp;
|
||||
struct null_mount *xmp;
|
||||
struct null_node *nn;
|
||||
@ -158,36 +158,24 @@ nullfs_mount(struct mount *mp)
|
||||
M_NULLFSMNT, M_WAITOK | M_ZERO);
|
||||
|
||||
/*
|
||||
* Save reference to underlying FS
|
||||
* Save pointer to underlying FS and the reference to the
|
||||
* lower root vnode.
|
||||
*/
|
||||
xmp->nullm_vfs = lowerrootvp->v_mount;
|
||||
vref(lowerrootvp);
|
||||
xmp->nullm_lowerrootvp = lowerrootvp;
|
||||
mp->mnt_data = xmp;
|
||||
|
||||
/*
|
||||
* Save reference. Each mount also holds
|
||||
* a reference on the root vnode.
|
||||
* Make sure the node alias worked.
|
||||
*/
|
||||
error = null_nodeget(mp, lowerrootvp, &vp);
|
||||
/*
|
||||
* Make sure the node alias worked
|
||||
*/
|
||||
if (error) {
|
||||
error = null_nodeget(mp, lowerrootvp, &nullm_rootvp);
|
||||
if (error != 0) {
|
||||
vrele(lowerrootvp);
|
||||
free(xmp, M_NULLFSMNT);
|
||||
return (error);
|
||||
}
|
||||
|
||||
/*
|
||||
* Keep a held reference to the root vnode.
|
||||
* It is vrele'd in nullfs_unmount.
|
||||
*/
|
||||
nullm_rootvp = vp;
|
||||
nullm_rootvp->v_vflag |= VV_ROOT;
|
||||
xmp->nullm_rootvp = nullm_rootvp;
|
||||
|
||||
/*
|
||||
* Unlock the node (either the lower or the alias)
|
||||
*/
|
||||
VOP_UNLOCK(vp);
|
||||
|
||||
if (NULLVPTOLOWERVP(nullm_rootvp)->v_mount->mnt_flag & MNT_LOCAL) {
|
||||
MNT_ILOCK(mp);
|
||||
mp->mnt_flag |= MNT_LOCAL;
|
||||
@ -209,7 +197,6 @@ nullfs_mount(struct mount *mp)
|
||||
mp->mnt_kern_flag |= lowerrootvp->v_mount->mnt_kern_flag &
|
||||
(MNTK_USES_BCACHE | MNTK_NO_IOPF | MNTK_UNMAPPED_BUFS);
|
||||
MNT_IUNLOCK(mp);
|
||||
mp->mnt_data = xmp;
|
||||
vfs_getnewfsid(mp);
|
||||
if ((xmp->nullm_flags & NULLM_CACHE) != 0) {
|
||||
MNT_ILOCK(xmp->nullm_vfs);
|
||||
@ -219,6 +206,7 @@ nullfs_mount(struct mount *mp)
|
||||
}
|
||||
|
||||
vfs_mountedfrom(mp, target);
|
||||
vput(nullm_rootvp);
|
||||
|
||||
NULLFSDEBUG("nullfs_mount: lower %s, alias at %s\n",
|
||||
mp->mnt_stat.f_mntfromname, mp->mnt_stat.f_mntonname);
|
||||
@ -235,7 +223,7 @@ nullfs_unmount(mp, mntflags)
|
||||
{
|
||||
struct null_mount *mntdata;
|
||||
struct mount *ump;
|
||||
int error, flags, rootrefs;
|
||||
int error, flags;
|
||||
|
||||
NULLFSDEBUG("nullfs_unmount: mp = %p\n", (void *)mp);
|
||||
|
||||
@ -244,9 +232,9 @@ nullfs_unmount(mp, mntflags)
|
||||
else
|
||||
flags = 0;
|
||||
|
||||
for (rootrefs = 1;; rootrefs = 0) {
|
||||
for (;;) {
|
||||
/* There is 1 extra root vnode reference (nullm_rootvp). */
|
||||
error = vflush(mp, rootrefs, flags, curthread);
|
||||
error = vflush(mp, 0, flags, curthread);
|
||||
if (error)
|
||||
return (error);
|
||||
MNT_ILOCK(mp);
|
||||
@ -273,6 +261,7 @@ nullfs_unmount(mp, mntflags)
|
||||
TAILQ_REMOVE(&ump->mnt_uppers, mp, mnt_upper_link);
|
||||
MNT_IUNLOCK(ump);
|
||||
}
|
||||
vrele(mntdata->nullm_lowerrootvp);
|
||||
mp->mnt_data = NULL;
|
||||
free(mntdata, M_NULLFSMNT);
|
||||
return (0);
|
||||
@ -285,21 +274,24 @@ nullfs_root(mp, flags, vpp)
|
||||
struct vnode **vpp;
|
||||
{
|
||||
struct vnode *vp;
|
||||
struct null_mount *mntdata;
|
||||
int error;
|
||||
|
||||
NULLFSDEBUG("nullfs_root(mp = %p, vp = %p->%p)\n", (void *)mp,
|
||||
(void *)MOUNTTONULLMOUNT(mp)->nullm_rootvp,
|
||||
(void *)NULLVPTOLOWERVP(MOUNTTONULLMOUNT(mp)->nullm_rootvp));
|
||||
mntdata = MOUNTTONULLMOUNT(mp);
|
||||
NULLFSDEBUG("nullfs_root(mp = %p, vp = %p)\n", mp,
|
||||
mntdata->nullm_lowerrootvp);
|
||||
|
||||
/*
|
||||
* Return locked reference to root.
|
||||
*/
|
||||
vp = MOUNTTONULLMOUNT(mp)->nullm_rootvp;
|
||||
VREF(vp);
|
||||
|
||||
ASSERT_VOP_UNLOCKED(vp, "root vnode is locked");
|
||||
vn_lock(vp, flags | LK_RETRY);
|
||||
*vpp = vp;
|
||||
return 0;
|
||||
error = vget(mntdata->nullm_lowerrootvp, (flags & ~LK_TYPE_MASK) |
|
||||
LK_EXCLUSIVE, curthread);
|
||||
if (error == 0) {
|
||||
error = null_nodeget(mp, mntdata->nullm_lowerrootvp, &vp);
|
||||
if (error == 0) {
|
||||
if ((flags & LK_TYPE_MASK) == LK_SHARED)
|
||||
vn_lock(vp, LK_DOWNGRADE | LK_RETRY);
|
||||
*vpp = vp;
|
||||
}
|
||||
}
|
||||
return (error);
|
||||
}
|
||||
|
||||
static int
|
||||
|
Loading…
Reference in New Issue
Block a user