mirror of
https://github.com/torvalds/linux.git
synced 2026-09-24 06:24:02 +02:00
nfsd: fix cpntf publish race in nfs4_init_cp_state
nfs4_alloc_init_cpntf_state() published the new cpntf entry into the
s2s_cp_stateids IDR (with cs_type set) in one s2s_cp_lock section, then
took the lock again to list_add() it onto p_stid->sc_cp_list. In the gap
the entry is reachable by so_id but cp_list is still {NULL,NULL} from
kzalloc. A racing OFFLOAD_CANCEL (so_id is echoed to the client as
cnr_stateid, so any NFSv4.2 client can drive it) reaches
manage_cpntf_state() -> _free_cpntf_state_locked() and does list_del() on
the zeroed list_head, oopsing the server.
Fold the cs_type assignment and the list_add() into the same critical
section as idr_alloc_cyclic(), so a concurrent lookup either misses the
entry or sees a fully linked cp_list. INIT_LIST_HEAD() the entry after
allocation and switch _free_cpntf_state_locked() to list_del_init() so a
stale unlink is a no-op. nfs4_init_copy_state() passes NULL p_stid and
skips the list_add, preserving NFS4_COPY_STID semantics.
Fixes: 624322f1ad ("NFSD add COPY_NOTIFY operation")
Cc: stable@vger.kernel.org
Assisted-by: kres:claude-opus-4-7
Signed-off-by: Chris Mason <clm@meta.com>
Link: https://patch.msgid.link/20260710-nfsd-testing-v3-1-a0ff7db6aa3e@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
parent
6480bd7036
commit
be3a5c1d85
|
|
@ -981,7 +981,7 @@ struct nfs4_stid *nfs4_alloc_stid(struct nfs4_client *cl, struct kmem_cache *sla
|
|||
* Create a unique stateid_t to represent each COPY.
|
||||
*/
|
||||
static int nfs4_init_cp_state(struct nfsd_net *nn, copy_stateid_t *stid,
|
||||
unsigned char cs_type)
|
||||
unsigned char cs_type, struct nfs4_stid *p_stid)
|
||||
{
|
||||
int new_id;
|
||||
|
||||
|
|
@ -991,19 +991,34 @@ static int nfs4_init_cp_state(struct nfsd_net *nn, copy_stateid_t *stid,
|
|||
idr_preload(GFP_KERNEL);
|
||||
spin_lock(&nn->s2s_cp_lock);
|
||||
new_id = idr_alloc_cyclic(&nn->s2s_cp_stateids, stid, 0, 0, GFP_NOWAIT);
|
||||
stid->cs_stid.si_opaque.so_id = new_id;
|
||||
stid->cs_stid.si_generation = 1;
|
||||
if (new_id >= 0) {
|
||||
stid->cs_stid.si_opaque.so_id = new_id;
|
||||
stid->cs_stid.si_generation = 1;
|
||||
/*
|
||||
* Set cs_type and link onto sc_cp_list under the same lock
|
||||
* that installed the IDR entry, so a concurrent
|
||||
* manage_cpntf_state() sees either no entry or a fully
|
||||
* linked cp_list.
|
||||
*/
|
||||
stid->cs_type = cs_type;
|
||||
if (p_stid) {
|
||||
struct nfs4_cpntf_state *cps =
|
||||
container_of(stid, struct nfs4_cpntf_state,
|
||||
cp_stateid);
|
||||
|
||||
list_add(&cps->cp_list, &p_stid->sc_cp_list);
|
||||
}
|
||||
}
|
||||
spin_unlock(&nn->s2s_cp_lock);
|
||||
idr_preload_end();
|
||||
if (new_id < 0)
|
||||
return 0;
|
||||
stid->cs_type = cs_type;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int nfs4_init_copy_state(struct nfsd_net *nn, struct nfsd4_copy *copy)
|
||||
{
|
||||
return nfs4_init_cp_state(nn, ©->cp_stateid, NFS4_COPY_STID);
|
||||
return nfs4_init_cp_state(nn, ©->cp_stateid, NFS4_COPY_STID, NULL);
|
||||
}
|
||||
|
||||
struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state(struct nfsd_net *nn,
|
||||
|
|
@ -1014,13 +1029,13 @@ struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state(struct nfsd_net *nn,
|
|||
cps = kzalloc_obj(struct nfs4_cpntf_state);
|
||||
if (!cps)
|
||||
return NULL;
|
||||
/* So a stale list_del_init() before linking is a no-op. */
|
||||
INIT_LIST_HEAD(&cps->cp_list);
|
||||
cps->cpntf_time = ktime_get_boottime_seconds();
|
||||
refcount_set(&cps->cp_stateid.cs_count, 1);
|
||||
if (!nfs4_init_cp_state(nn, &cps->cp_stateid, NFS4_COPYNOTIFY_STID))
|
||||
if (!nfs4_init_cp_state(nn, &cps->cp_stateid, NFS4_COPYNOTIFY_STID,
|
||||
p_stid))
|
||||
goto out_free;
|
||||
spin_lock(&nn->s2s_cp_lock);
|
||||
list_add(&cps->cp_list, &p_stid->sc_cp_list);
|
||||
spin_unlock(&nn->s2s_cp_lock);
|
||||
return cps;
|
||||
out_free:
|
||||
kfree(cps);
|
||||
|
|
@ -7959,7 +7974,7 @@ _free_cpntf_state_locked(struct nfsd_net *nn, struct nfs4_cpntf_state *cps)
|
|||
WARN_ON_ONCE(cps->cp_stateid.cs_type != NFS4_COPYNOTIFY_STID);
|
||||
if (!refcount_dec_and_test(&cps->cp_stateid.cs_count))
|
||||
return;
|
||||
list_del(&cps->cp_list);
|
||||
list_del_init(&cps->cp_list);
|
||||
idr_remove(&nn->s2s_cp_stateids,
|
||||
cps->cp_stateid.cs_stid.si_opaque.so_id);
|
||||
kfree(cps);
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user