diff --git a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c
--- a/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c
+++ b/sys/contrib/openzfs/module/os/freebsd/zfs/zfs_vnops_os.c
@@ -6321,9 +6321,7 @@
 bad_write_fallback:
 	if (mp != NULL)
 		vn_finished_write(mp);
-	error = vn_generic_copy_file_range(ap->a_invp, ap->a_inoffp,
-	    ap->a_outvp, ap->a_outoffp, ap->a_lenp, ap->a_flags,
-	    ap->a_incred, ap->a_outcred, ap->a_fsizetd);
+	error = ENOSYS;
 	return (error);
 }
 
diff --git a/sys/fs/fuse/fuse_vnops.c b/sys/fs/fuse/fuse_vnops.c
--- a/sys/fs/fuse/fuse_vnops.c
+++ b/sys/fs/fuse/fuse_vnops.c
@@ -861,6 +861,7 @@
 	pid_t pid;
 	int err;
 
+	err = ENOSYS;
 	if (mp == NULL || mp != vnode_mount(outvp))
 		goto fallback;
 
@@ -943,13 +944,9 @@
 		VOP_UNLOCK(invp);
 	VOP_UNLOCK(outvp);
 
-	if (err == ENOSYS) {
+	if (err == ENOSYS)
 		fsess_set_notimpl(mp, FUSE_COPY_FILE_RANGE);
 fallback:
-		err = vn_generic_copy_file_range(ap->a_invp, ap->a_inoffp,
-		    ap->a_outvp, ap->a_outoffp, ap->a_lenp, ap->a_flags,
-		    ap->a_incred, ap->a_outcred, ap->a_fsizetd);
-	}
 
 	/*
 	 * No need to call vn_rlimit_fsizex_res before return, since the uio is
diff --git a/sys/fs/nfsclient/nfs_clvnops.c b/sys/fs/nfsclient/nfs_clvnops.c
--- a/sys/fs/nfsclient/nfs_clvnops.c
+++ b/sys/fs/nfsclient/nfs_clvnops.c
@@ -3888,9 +3888,7 @@
 	 */
 	if (invp == outvp || invp->v_mount != outvp->v_mount) {
 generic_copy:
-		return (vn_generic_copy_file_range(invp, ap->a_inoffp,
-		    outvp, ap->a_outoffp, ap->a_lenp, ap->a_flags,
-		    ap->a_incred, ap->a_outcred, ap->a_fsizetd));
+		return (ENOSYS);
 	}
 
 	/* Lock both vnodes, avoiding risk of deadlock. */
diff --git a/sys/fs/nullfs/null_vnops.c b/sys/fs/nullfs/null_vnops.c
--- a/sys/fs/nullfs/null_vnops.c
+++ b/sys/fs/nullfs/null_vnops.c
@@ -1131,6 +1131,23 @@
 	return (res);
 }
 
+static int
+null_getlowvnode(struct vop_getlowvnode_args *ap)
+{
+	struct vnode *vp, *vpl;
+
+	vp = ap->a_vp;
+	if (vn_lock(vp, LK_SHARED) != 0)
+		return (EBADF);
+
+	vpl = NULLVPTOLOWERVP(vp);
+	vhold(vpl);
+	VOP_UNLOCK(vp);
+	VOP_GETLOWVNODE(vpl, ap->a_vplp, ap->a_flags);
+	vdrop(vpl);
+	return (0);
+}
+
 /*
  * Global vfs data structures
  */
@@ -1143,6 +1160,7 @@
 	.vop_bmap =		VOP_EOPNOTSUPP,
 	.vop_stat =		null_stat,
 	.vop_getattr =		null_getattr,
+	.vop_getlowvnode =	null_getlowvnode,
 	.vop_getwritemount =	null_getwritemount,
 	.vop_inactive =		null_inactive,
 	.vop_need_inactive =	null_need_inactive,
@@ -1163,5 +1181,6 @@
 	.vop_vptofh =		null_vptofh,
 	.vop_add_writecount =	null_add_writecount,
 	.vop_vput_pair =	null_vput_pair,
+	.vop_copy_file_range =	VOP_PANIC,
 };
 VFS_VOP_VECTOR_REGISTER(null_vnodeops);
diff --git a/sys/kern/vfs_default.c b/sys/kern/vfs_default.c
--- a/sys/kern/vfs_default.c
+++ b/sys/kern/vfs_default.c
@@ -85,6 +85,7 @@
 static int vop_stdread_pgcache(struct vop_read_pgcache_args *ap);
 static int vop_stdstat(struct vop_stat_args *ap);
 static int vop_stdvput_pair(struct vop_vput_pair_args *ap);
+static int vop_stdgetlowvnode(struct vop_getlowvnode_args *ap);
 
 /*
  * This vnode table stores what we want to do if the filesystem doesn't
@@ -115,6 +116,7 @@
 	.vop_fsync =		VOP_NULL,
 	.vop_stat =		vop_stdstat,
 	.vop_fdatasync =	vop_stdfdatasync,
+	.vop_getlowvnode = 	vop_stdgetlowvnode,
 	.vop_getpages =		vop_stdgetpages,
 	.vop_getpages_async =	vop_stdgetpages_async,
 	.vop_getwritemount = 	vop_stdgetwritemount,
@@ -1610,3 +1612,11 @@
 		vput(vp);
 	return (0);
 }
+
+static int
+vop_stdgetlowvnode(struct vop_getlowvnode_args *ap)
+{
+	vref(ap->a_vp);
+	*ap->a_vplp = ap->a_vp;
+	return (0);
+}
diff --git a/sys/kern/vfs_vnops.c b/sys/kern/vfs_vnops.c
--- a/sys/kern/vfs_vnops.c
+++ b/sys/kern/vfs_vnops.c
@@ -3072,10 +3072,12 @@
     struct ucred *outcred, struct thread *fsize_td)
 {
 	struct mount *inmp, *outmp;
+	struct vnode *invpl, *outvpl;
 	int error;
 	size_t len;
 	uint64_t uval;
 
+	invpl = outvpl = NULL;
 	len = *lenp;
 	*lenp = 0;		/* For error returns. */
 	error = 0;
@@ -3101,17 +3103,22 @@
 	if (len == 0)
 		goto out;
 
-	inmp = invp->v_mount;
-	outmp = outvp->v_mount;
-	if (inmp == NULL || outmp == NULL) {
-		error = EBADF;
+	error = VOP_GETLOWVNODE(invp, &invpl, FREAD);
+	if (error != 0)
 		goto out;
-	}
+	error = VOP_GETLOWVNODE(outvp, &outvpl, FWRITE);
+	if (error != 0)
+		goto out1;
+
+	inmp = invpl->v_mount;
+	outmp = outvpl->v_mount;
+	if (inmp == NULL || outmp == NULL)
+		goto out2;
 
 	for (;;) {
 		error = vfs_busy(inmp, 0);
 		if (error != 0)
-			goto out;
+			goto out2;
 		if (inmp == outmp)
 			break;
 		error = vfs_busy(outmp, MBF_NOWAIT);
@@ -3122,7 +3129,7 @@
 				vfs_unbusy(outmp);
 				continue;
 			}
-			goto out;
+			goto out2;
 		}
 		break;
 	}
@@ -3133,16 +3140,23 @@
 	 * which can handle copies across multiple file system types.
 	 */
 	*lenp = len;
-	if (inmp == outmp || strcmp(inmp->mnt_vfc->vfc_name,
-	    outmp->mnt_vfc->vfc_name) == 0)
-		error = VOP_COPY_FILE_RANGE(invp, inoffp, outvp, outoffp,
+	if (inmp == outmp || inmp->mnt_vfc == outmp->mnt_vfc)
+		error = VOP_COPY_FILE_RANGE(invpl, inoffp, outvpl, outoffp,
 		    lenp, flags, incred, outcred, fsize_td);
 	else
-		error = vn_generic_copy_file_range(invp, inoffp, outvp,
+		error = ENOSYS;
+	if (error == ENOSYS)
+		error = vn_generic_copy_file_range(invpl, inoffp, outvpl,
 		    outoffp, lenp, flags, incred, outcred, fsize_td);
 	vfs_unbusy(outmp);
 	if (inmp != outmp)
 		vfs_unbusy(inmp);
+out2:
+	if (outvpl != NULL)
+		vrele(outvpl);
+out1:
+	if (invpl != NULL)
+		vrele(invpl);
 out:
 	return (error);
 }
diff --git a/sys/kern/vnode_if.src b/sys/kern/vnode_if.src
--- a/sys/kern/vnode_if.src
+++ b/sys/kern/vnode_if.src
@@ -471,6 +471,13 @@
 	OUT struct mount **mpp;
 };
 
+%% getwritevnode vp	= = =
+
+vop_getlowvnode {
+	IN struct vnode *vp;
+	OUT struct vnode **vplp;
+	IN int flags;
+};
 
 %% print	vp	- - -