DF-2774 / fix.diff
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 | --- a/sys/kern/vfs_mount.c 2026-09-01 02:05:26.425046951 +0000 +++ b/sys/kern/vfs_mount.c 2026-09-01 02:07:28.231508072 +0000 @@ -138,6 +138,7 @@ static struct lwkt_token mntid_token; static struct mount dummymount; +static volatile uint64_t mnt_cookie_gen; /* note: mountlist exported to pstat */ struct mntlist mountlist = TAILQ_HEAD_INITIALIZER(mountlist); @@ -380,6 +381,7 @@ TAILQ_INIT(&mp->mnt_nvnodelist); TAILQ_INIT(&mp->mnt_reservedvnlist); TAILQ_INIT(&mp->mnt_jlist); + mp->mnt_cookie = atomic_fetchadd_64(&mnt_cookie_gen, 1) + 1; mp->mnt_nvnodelistsize = 0; mp->mnt_flag = 0; mp->mnt_hold = 1; /* hold for umount last drop */ @@ -708,6 +710,39 @@ } /* + * mountlist_hold (MP SAFE) + * + * Like mountlist_exists(), but if the mount is still on the mountlist + * a hold is acquired so the caller can safely use the returned mount. + * mountlist_exists() only compares pointers and gives the caller no + * lifetime guarantee whatsoever (a mount that is concurrently being + * unmounted can be ripped off the list and freed right after the + * check, and a mount reusing the freed structure's address makes the + * check succeed against the wrong mount). + * + * Returns the held mount (caller must mount_drop()), or NULL if the + * mount is no longer on the mountlist. + */ +struct mount * +mountlist_hold(struct mount *mp) +{ + struct mount *found = NULL; + struct mount *lmp; + + lwkt_gettoken(&mountlist_token); + TAILQ_FOREACH(lmp, &mountlist, mnt_list) { + if (lmp == mp) { + mount_hold(lmp); + found = lmp; + break; + } + } + lwkt_reltoken(&mountlist_token); + + return (found); +} + +/* * mountlist_scan * * Safely scan the mount points on the mount list. Each mountpoint --- a/sys/kern/vfs_quota.c 2026-09-01 02:05:26.425046951 +0000 +++ b/sys/kern/vfs_quota.c 2026-09-01 02:08:11.098966457 +0000 @@ -420,16 +420,29 @@ struct mount* vq_vptomp(struct vnode *vp) { - /* XXX: vp->v_pfsmp may point to a freed structure - * we use mountlist_exists() to check if it is valid - * before using it */ - if ((vp->v_pfsmp != NULL) && (mountlist_exists(vp->v_pfsmp))) { - /* This is a PFS, use a copy of the real mp */ - return vp->v_pfsmp; - } else { - /* Not a PFS or a PFS beeing unmounted */ - return vp->v_mount; + struct mount *mp; + + /* + * XXX: vp->v_pfsmp may point to a mount that has already been + * unmounted and freed, or to a *different* mount that reused + * the freed struct mount's address. Take a real hold and + * validate the generation cookie before using it. + * + * Returns a held mount; the caller must mount_drop() it. + */ + if (vp->v_pfsmp != NULL) { + mp = mountlist_hold(vp->v_pfsmp); + if (mp != NULL) { + if (mp->mnt_cookie == vp->v_pfsmp_cookie) + return mp; /* held */ + mount_drop(mp); /* different mount reused the addr */ + } } + /* Not a PFS or the PFS is beeing unmounted */ + mp = vp->v_mount; + mount_hold(mp); + + return mp; } int --- a/sys/kern/vfs_vnops.c 2026-09-01 02:05:26.429046900 +0000 +++ b/sys/kern/vfs_vnops.c 2026-09-01 02:05:37.624905461 +0000 @@ -324,6 +324,7 @@ goto bad; mp = vq_vptomp(vp); VFS_ACCOUNT(mp, vap->va_uid, vap->va_gid, -osize); + mount_drop(mp); } /* --- a/sys/kern/vfs_vopops.c 2026-09-01 02:05:26.429046900 +0000 +++ b/sys/kern/vfs_vopops.c 2026-09-01 02:05:42.544843305 +0000 @@ -456,7 +456,7 @@ int error, do_accounting = 0; struct vattr va; uint64_t size_before=0, size_after=0; - struct mount *mp; + struct mount *mp = NULL; uint64_t offset, delta; ap.a_head.a_desc = &vop_write_desc; @@ -493,9 +493,12 @@ } DO_OPS(ops, error, &ap, vop_write); if ((error == 0) && do_accounting) { + KKASSERT(mp != NULL); VFS_ACCOUNT(mp, va.va_uid, va.va_gid, size_after - size_before); } done: + if (mp) + mount_drop(mp); VFS_MPUNLOCK(); return(error); --- a/sys/kern/vfs_cache.c 2026-09-01 02:07:20.219609296 +0000 +++ b/sys/kern/vfs_cache.c 2026-09-01 02:07:29.823487957 +0000 @@ -1384,8 +1384,10 @@ * implementation */ if (mp) { - if (strncmp(mp->mnt_stat.f_fstypename, "null", 5) == 0) + if (strncmp(mp->mnt_stat.f_fstypename, "null", 5) == 0) { vp->v_pfsmp = mp; + vp->v_pfsmp_cookie = mp->mnt_cookie; + } } } else { /* --- a/sys/sys/mount.h 2026-09-01 02:05:26.433046850 +0000 +++ b/sys/sys/mount.h 2026-09-01 02:07:25.379544103 +0000 @@ -219,6 +219,7 @@ struct vfsconf *mnt_vfc; /* configuration info */ u_int mnt_namecache_gen; /* ++ to clear negative hits */ u_int mnt_pbuf_count; /* pbuf usage limit */ + uint64_t mnt_cookie; /* unique generation cookie */ struct vnode *mnt_syncer; /* syncer vnode */ struct syncer_ctx *mnt_syncer_ctx; /* syncer process context */ struct vnodelst mnt_nvnodelist; /* list of vnodes this mount */ @@ -806,6 +807,7 @@ struct mount *mountlist_boot_getfirst(void); void mountlist_remove(struct mount *mp); int mountlist_exists(struct mount *mp); +struct mount *mountlist_hold(struct mount *mp); int mountlist_scan(int (*callback)(struct mount *, void *), void *, int); struct mount *mount_get_by_nc(struct namecache *ncp); #else /* !_KERNEL */ --- a/sys/sys/vnode.h 2026-09-01 02:07:20.219609296 +0000 +++ b/sys/sys/vnode.h 2026-09-01 02:07:23.955562095 +0000 @@ -191,6 +191,7 @@ } v_pollinfo; struct vmresident *v_resident; /* optional vmresident */ struct mount *v_pfsmp; /* real mp for pfs/nullfs mt */ + uint64_t v_pfsmp_cookie; /* generation of v_pfsmp */ struct timespec v_lastwrite_ts; /* async mmap flush ts */ }; #define v_socket v_un.vu_socket |