Re: [REGRESSION] 7.2.4: nfsd sends CB_OFFLOAD with all-zero stateid, async NFSv4.2 COPY never completes
From: Jeff Layton
Date: Sun Oct 11 2026 - 08:07:52 EST
On Sun, 2026-10-11 at 09:19 +0200, Greg KH wrote:
> On Sun, Oct 11, 2026 at 06:30:40AM +1100, Lubos Rendek wrote:
> > Since 7.2.4, an NFSv4.2 server-side COPY between two exports of a Linux
> > NFS server never completes, because nfsd sends CB_OFFLOAD with an
> > all-zero stateid.
>
> Can you just use git bisect to determine the offending commit?
>
> And is this also the same for 7.3-rc?
>
>
I took a look: I believe this is a bug in 14b978e8d05c ("nfsd: fix
stale s2s_cp_stateids IDR entry for async COPY"), which is d0beaee498e1
in mainline.
In v7.3, this bug gets fixed by f307b4f7ed17 ("nfsd: split nfsd4_copy
into transient and durable async copy objects"), which was part of the
refactor of the async COPY code.
We should probably just take Lubos' two-line fix as a separate one-off
patch for stable. Backporting the COPY code refactor is too risky IMO.
Clanker analysis follows if you're interested. I think it's largely
correct:
-----------------8<-----------------------
◆ Diagnosis confirmed: the report is correct, and this is a stable-only regression from an
incomplete backport.
Root cause is the order of dup_copy_fields() vs nfs4_init_copy_state() in nfsd4_copy():
• result points at the transient &u->copy.cp_res.
• dup_copy_fields() copies cp_res (including cb_stateid) from transient to async_copy.
• nfsd4_send_cb_offload() builds CB_OFFLOAD from async_copy->cp_res.
Pre-d0beaee498e1, mainline did init → write result->cb_stateid → dup_copy_fields(), so both the
COPY reply and async_copy->cp_res.cb_stateid got the stateid. But the s2s_cp_stateids IDR stored a
pointer to &u->copy.cp_stateid, which is transient COMPOUND buffer memory.
d0beaee498e1 ("nfsd: fix stale s2s_cp_stateids IDR entry for async COPY", stable 14b978e8d05c)
fixed the IDR lifetime by doing dup_copy_fields() first, then
nfs4_init_copy_state(nn, async_copy), then writing only result->cb_stateid. That leaves
async_copy->cp_res.cb_stateid zero, because the dup already happened. Result: COPY reply has a
valid stateid, CB_OFFLOAD has all-zero, client can't match it in nfs42_proc_copy and hangs. This
matches your packet table exactly.
Mainline is fixed by f307b4f7ed17 ("nfsd: split nfsd4_copy into transient and durable async copy
objects"), which moves the durable stateid out of the duplicated struct so it can safely do init →
write cb_stateid → dup again. Current code in fs/nfsd/nfs4proc.c:2242 has the explicit comment
"dup after writing cb_stateid; duplicating first would leave the callback stateid zeroed." I
verified the callback path in fs/nfsd/nfs4proc.c:2104 copies from cp_copy.cp_res, and
dup_copy_fields in fs/nfsd/nfs4proc.c:2051 copies cp_res.
Your minimal stable fix is the right shape for stable. A plain reorder won't work on the old
layout because dup_copy_fields() also copies cp_stateid, so init-then-dup would clobber
async_copy->cp_stateid with the transient's zero value. Copying the new stateid into both
result->cb_stateid and async_copy->cp_res.cb_stateid after init preserves the IDR fix and fixes
the callback. The alternative is backporting the full restructuring series (f307b4f7ed17,
80c376183411, etc.), which is heavier for stable.
#regzbot introduced: v7.2.3..v7.2.4 looks correct. The 6.12 sync-copy observation is expected:
that branch largely forces COPY synchronous, so it doesn't reach the async/CB_OFFLOAD path.
I did not run the reproducer or inspect a 7.2.y tree locally; verification was by reading mainline
and the d0beaee498e1 before/after versions in this checkout.
--
Jeff Layton <jlayton@xxxxxxxxxx>