diff --git a/sys/vfs/nfs/nfs_vnops.c b/sys/vfs/nfs/nfs_vnops.c --- a/sys/vfs/nfs/nfs_vnops.c +++ b/sys/vfs/nfs/nfs_vnops.c @@ -3085,8 +3085,24 @@ np->n_fhsize = fhlen; newvp = NFSTOV(np); } else if (NFS_CMPFH(dnp, nfhp, fhlen)) { - vref(dvp); - newvp = dvp; + /* + * The server echoed the parent directory's filehandle for this + * lookup. This branch is only reached when *npp == NULL (the + * first 'if (*npp)' handles the update-existing case), i.e. the + * create-style callers (nfs_create / nfs_mkdir / nfs_mknodrpc / + * nfs_symlink) that asked nfs_lookitup() to allocate a NEW + nfsnode. Returning the directory vnode as the "new" object + would corrupt those callers' vnode lock/ref accounting, and the + local 'np' being left uninitialized here would (bug) cause the + trailing '*npp = np' to store stack garbage which the caller + dereferences via NFSTOV(np). Treat the echoed handle as a + collision so the create callers see EEXIST instead of panicking. + A correct server never triggers this path (a post-create LOOKUP + returns the new object's own filehandle, not the parent's). + */ + m_freem(info.mrep); + info.mrep = NULL; + return (EEXIST); } else { error = nfs_nget(dvp->v_mount, nfhp, fhlen, &np, NULL); if (error) {