DF-5788 / fix.diff
--- a/sys/kern/vfs_cache.c +++ b/sys/kern/vfs_cache.c @@ -5070,10 +5070,22 @@ kern_getcwd(char *buf, size_t buflen, int *error) fdp = p->p_fd; slash_prefixed = 0; + /* + * DF-5788: Snapshot the cwd nchandle under fd_spin (the same lock + * chdir/fchdir/fdfree use to swap fd_ncdir) and take a reference on + * the mount as well as the ncp. The walk below dereferences + * nch.mount (mnt_ncmountpt / mnt_ncmounton) on every component; + * without a mount reference a concurrent umount -f can free the + * mount structure out from under the walk (use-after-free), and + * without fd_spin the snapshot races chdir's cache_drop of the old + * handle. + */ + spin_lock_shared(&fdp->fd_spin); nch = fdp->fd_ncdir; ncp = nch.ncp; if (ncp) _cache_hold(ncp); + if (nch.mount) + _cache_mntref(nch.mount); + spin_unlock_shared(&fdp->fd_spin); while (ncp && (ncp != fdp->fd_nrdir.ncp || nch.mount != fdp->fd_nrdir.mount) @@ -5088,10 +5100,26 @@ kern_getcwd(char *buf, size_t buflen, int *error) * While traversing upwards if we encounter the root * of the current mount we have to skip to the mount point * in the underlying filesystem. + * + * nch.mount is referenced by us so the mount structure + * cannot be freed while we read it. dounmount() may be + * concurrently clearing mnt_ncmounton (cache_zero), so + * reference the new mount before releasing the old one and + * end the walk gracefully if we catch the transition + * half-done (this also fixes a latent NULL deref in the + * original code, which only avoided it by reading freed + * memory). */ if (ncp == nch.mount->mnt_ncmountpt.ncp) { + struct mount *cur_mp = nch.mount; + nch = nch.mount->mnt_ncmounton; + if (nch.ncp == NULL || nch.mount == NULL) { + /* mount being torn down mid-read; end the + * walk, releasing the ref we still hold */ + _cache_mntrel(cur_mp); + nch.ncp = NULL; + nch.mount = NULL; + _cache_drop(ncp); + ncp = NULL; + break; + } + _cache_mntref(nch.mount); _cache_drop(ncp); ncp = nch.ncp; if (ncp) _cache_hold(ncp); + _cache_mntrel(cur_mp); continue; } @@ -5156,6 +5184,10 @@ kern_getcwd(char *buf, size_t buflen, int *error) *error = 0; done: if (ncp) _cache_drop(ncp); + /* DF-5788: release the mount reference held for the walk (none + * if we never took one or broke out at a torn transition). */ + if (nch.mount) + _cache_mntrel(nch.mount); return (bp); } |