FAILED: patch "[PATCH] nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x ca94ba36172046be6a694a7986f6931e47ed4d51
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026090305-dowry-plaster-fff3@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From ca94ba36172046be6a694a7986f6931e47ed4d51 Mon Sep 17 00:00:00 2001
From: Jeff Layton <[email protected]>
Date: Sat, 30 May 2026 09:19:17 -0400
Subject: [PATCH] nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing
delegation revoke
nfsd4_alloc_layout_stateid reads fp->fi_deleg_file without holding fi_lock when the parent stateid is a delegation. A concurrent delegation revoke via the laundromat can clear fi_deleg_file under fi_lock, causing nfsd_file_get() to return NULL and triggering the BUG_ON.
This race is client-reachable: two NFS clients can trigger it by having one hold a delegation while another opens the same file to force a recall. When the first client doesn't respond to the recall, the laundromat revokes it. A concurrent LAYOUTGET from any client using the delegation stateid hits the race window.
Fix this by taking fi_lock around the fi_deleg_file read in the SC_TYPE_DELEG path, matching the locking discipline of the find_any_file() arm, and replacing the BUG_ON with a graceful error return that cleans up the partially-initialized layout stateid.
fs/nfsd/nfs4layouts.c +9 -3
@@ -247,11 +247,17 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate,nfsd4_init_cb (&ls->ls_recall, clp, &nfsd4_cb_layout_ops,NFSPROC4_CLNT_CB_LAYOUT ); -if (parent->sc_type ==SC_TYPE_DELEG ) +if (parent->sc_type ==SC_TYPE_DELEG ) { +spin_lock (&fp->fi_lock); ls->ls_file =nfsd_file_get (fp->fi_deleg_file); -else +spin_unlock (&fp->fi_lock); + }else { ls->ls_file =find_any_file (fp); -BUG_ON (!ls->ls_file); + } +if (!ls->ls_file) { +nfs4_put_stid (stp); +return NULL ; + } ls->ls_fenced = false; ls->ls_fence_delay =0 ;
Sasha Levin
maintainerPATCH 1/7 NFSD: Add simple u32, u64, and bool encoders
From: Chuck Lever <[email protected]>
[ Upstream commit 6cc58291408bea96787d06ca3f5074fdfb3152ca ]
The generic XDR encoders return a length or a negative errno. NFSv4 encoders want to know simply whether the encode ran out of stream buffer space. The return values for server-side encoding are either nfs_ok or nfserr_resource.
So far I've found it adds a lot of duplicate code to try to use the generic XDR encoder utilities when encoding the simple data types in the NFSv4 operation encoders.
Add a set of NFSv4-specific utilities that handle the basic XDR data types. These are added in xdr4.h so they might eventually be used by the callback server and pNFS driver encoders too.
Stable-dep-of: ca94ba361720 ("nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke")
--- fs/nfsd/xdr4.h | 111 +++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+)
fs/nfsd/xdr4.h +111 -0
@@ -50,6 +50,117 @@#define HAS_CSTATE_FLAG (c, f) ((c)->sid_flags & (f))#define CLEAR_CSTATE_FLAG (c, f) ((c)->sid_flags &= ~(f)) +/** +* nfsd4_encode_bool - Encode an XDR bool type result +* @xdr: target XDR stream +* @val: boolean value to encode +* +* Return values: +* %nfs_ok: @val encoded; @xdr advanced to next position +* %nfserr_resource: stream buffer space exhausted +*/ +static __always_inline __be32 +nfsd4_encode_bool (struct xdr_stream *xdr,bool val) +{ +__be32 *p =xdr_reserve_space (xdr,XDR_UNIT ); + +if (unlikely (p ==NULL )) +return nfserr_resource; + *p = val ? xdr_one : xdr_zero; +return nfs_ok; +} + +/** +* nfsd4_encode_uint32_t - Encode an XDR uint32_t type result +* @xdr: target XDR stream +* @val: integer value to encode +* +* Return values: +* %nfs_ok: @val encoded; @xdr advanced to next position +* %nfserr_resource: stream buffer space exhausted +*/ +static __always_inline __be32 +nfsd4_encode_uint32_t (struct xdr_stream *xdr,u32 val) +{ +__be32 *p =xdr_reserve_space (xdr,XDR_UNIT ); + +if (unlikely (p ==NULL )) +return nfserr_resource; + *p =cpu_to_be32 (val); +return nfs_ok; +} + +/** +* nfsd4_encode_uint64_t - Encode an XDR uint64_t type result +* @xdr: target XDR stream +* @val: integer value to encode +* +* Return values: +* %nfs_ok: @val encoded; @xdr advanced to next position +* %nfserr_resource: stream buffer space exhausted +*/ +static __always_inline __be32 +nfsd4_encode_uint64_t (struct xdr_stream *xdr,u64 val) +{ +__be32 *p =xdr_reserve_space (xdr,XDR_UNIT *2 ); + +if (unlikely (p ==NULL )) +return nfserr_resource; +put_unaligned_be64 (val, p); +return nfs_ok; +} + +/** +* nfsd4_encode_opaque_fixed - Encode a fixed-length XDR opaque type result +* @xdr: target XDR stream +* @data: pointer to data +* @size: length of data in bytes +* +* Return values: +* %nfs_ok: @data encoded; @xdr advanced to next position +* %nfserr_resource: stream buffer space exhausted +*/ +static __always_inline __be32 +nfsd4_encode_opaque_fixed (struct xdr_stream *xdr,const void *data, +size_t size) +{ +__be32 *p =xdr_reserve_space (xdr,xdr_align_size (size)); +size_t pad =xdr_pad_size (size); + +if (unlikely (p ==NULL )) +return nfserr_resource; +memcpy (p, data, size); +if (pad) +memset ((char *)p + size,0 , pad); +return nfs_ok; +} + +/** +* nfsd4_encode_opaque - Encode a variable-length XDR opaque type result +* @xdr: target XDR stream +* @data: pointer to data +* @size: length of data in bytes +* +* Return values: +* %nfs_ok: @data encoded; @xdr advanced to next position +* %nfserr_resource: stream buffer space exhausted +*/ +static __always_inline __be32 +nfsd4_encode_opaque (struct xdr_stream *xdr,const void *data,size_t size) +{ +size_t pad =xdr_pad_size (size); +__be32 *p; + + p =xdr_reserve_space (xdr,XDR_UNIT +xdr_align_size (size)); +if (unlikely (p ==NULL )) +return nfserr_resource; + *p++ =cpu_to_be32 (size); +memcpy (p, data, size); +if (pad) +memset ((char *)p + size,0 , pad); +return nfs_ok; +} +struct nfsd4_compound_state {struct svc_fh current_fh;struct svc_fh save_fh;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 2/7 NFSD: Clean up nfsd4_encode_stateid()
From: Chuck Lever <[email protected]>
[ Upstream commit 40bb2baaa8edecfc21a3c176e4af1a3445157677 ]
Update the encoder function name to match the type name, as is the convention with other such encoder utility functions, and with nfsd4_decode_stateid4().
Make the @stateid argument a const so that callers of nfsd4_encode_stateid4() in the future can be passed const pointers to structures.
Since the compiler is allowed to add padding to structs, use the wire (spec-defined) size when reserving buffer space.
Stable-dep-of: ca94ba361720 ("nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke")
--- fs/nfsd/nfs4xdr.c | 36 ++++++++++++++++++++---------------- 1 file changed, 20 insertions(+), 16 deletions(-)
fs/nfsd/nfs4xdr.c +20 -16
@@ -3713,18 +3713,18 @@ nfsd4_encode_clientid4(struct xdr_stream *xdr, const clientid_t *clientid)return nfs_ok; } +/* This is a frequently-encoded item; open-coded for speed */ static __be32 -nfsd4_encode_stateid (struct xdr_stream *xdr,stateid_t *sid) +nfsd4_encode_stateid4 (struct xdr_stream *xdr,const stateid_t *sid) {__be32 *p; - p =xdr_reserve_space (xdr,sizeof (stateid_t)); + p =xdr_reserve_space (xdr,NFS4_STATEID_SIZE );if (!p)return nfserr_resource; *p++ =cpu_to_be32 (sid->si_generation); - p =xdr_encode_opaque_fixed (p, &sid->si_opaque, -sizeof (stateid_opaque_t)); -return 0 ; +memcpy (p, &sid->si_opaque,sizeof (sid->si_opaque)); +return nfs_ok; } static __be32
@@ -3768,7 +3768,8 @@ nfsd4_encode_close(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_close *close = &u->close;struct xdr_stream *xdr = resp->xdr; -return nfsd4_encode_stateid (xdr, &close->cl_stateid); +/* open_stateid */ +return nfsd4_encode_stateid4 (xdr, &close->cl_stateid); }
@@ -3872,7 +3873,7 @@ nfsd4_encode_lock(struct nfsd4_compoundres *resp, __be32 nfserr,struct xdr_stream *xdr = resp->xdr;if (!nfserr) - nfserr =nfsd4_encode_stateid (xdr, &lock->lk_resp_stateid); + nfserr =nfsd4_encode_stateid4 (xdr, &lock->lk_resp_stateid);else if (nfserr == nfserr_denied) nfserr =nfsd4_encode_lock_denied (xdr, &lock->lk_denied);
@@ -3898,7 +3899,8 @@ nfsd4_encode_locku(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_locku *locku = &u->locku;struct xdr_stream *xdr = resp->xdr; -return nfsd4_encode_stateid (xdr, &locku->lu_stateid); +/* lock_stateid */ +return nfsd4_encode_stateid4 (xdr, &locku->lu_stateid); }
@@ -3921,7 +3923,7 @@ nfsd4_encode_open(struct nfsd4_compoundres *resp, __be32 nfserr,struct xdr_stream *xdr = resp->xdr;__be32 *p; - nfserr =nfsd4_encode_stateid (xdr, &open->op_stateid); + nfserr =nfsd4_encode_stateid4 (xdr, &open->op_stateid);if (nfserr)return nfserr; nfserr =nfsd4_encode_change_info4 (xdr, &open->op_cinfo);
@@ -3944,7 +3946,7 @@ nfsd4_encode_open(struct nfsd4_compoundres *resp, __be32 nfserr,case NFS4_OPEN_DELEGATE_NONE :break ;case NFS4_OPEN_DELEGATE_READ : - nfserr =nfsd4_encode_stateid (xdr, &open->op_delegate_stateid); + nfserr =nfsd4_encode_stateid4 (xdr, &open->op_delegate_stateid);if (nfserr)return nfserr; p =xdr_reserve_space (xdr,20 );
@@ -3961,7 +3963,7 @@ nfsd4_encode_open(struct nfsd4_compoundres *resp, __be32 nfserr, *p++ =cpu_to_be32 (0 );/* XXX: is NULL principal ok? */ break ;case NFS4_OPEN_DELEGATE_WRITE : - nfserr =nfsd4_encode_stateid (xdr, &open->op_delegate_stateid); + nfserr =nfsd4_encode_stateid4 (xdr, &open->op_delegate_stateid);if (nfserr)return nfserr;
@@ -4019,7 +4021,8 @@ nfsd4_encode_open_confirm(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_open_confirm *oc = &u->open_confirm;struct xdr_stream *xdr = resp->xdr; -return nfsd4_encode_stateid (xdr, &oc->oc_resp_stateid); +/* open_stateid */ +return nfsd4_encode_stateid4 (xdr, &oc->oc_resp_stateid); } static __be32
@@ -4029,7 +4032,8 @@ nfsd4_encode_open_downgrade(struct nfsd4_compoundres *resp, __be32 nfserr,struct nfsd4_open_downgrade *od = &u->open_downgrade;struct xdr_stream *xdr = resp->xdr; -return nfsd4_encode_stateid (xdr, &od->od_stateid); +/* open_stateid */ +return nfsd4_encode_stateid4 (xdr, &od->od_stateid); }/*
@@ -4773,7 +4777,7 @@ nfsd4_encode_layoutreturn(struct nfsd4_compoundres *resp, __be32 nfserr,return nfserr_resource; *p++ =cpu_to_be32 (lrp->lrs_present);if (lrp->lrs_present) -return nfsd4_encode_stateid (xdr, &lrp->lr_sid); +return nfsd4_encode_stateid4 (xdr, &lrp->lr_sid); return 0; }#endif /* CONFIG_NFSD_PNFS */
@@ -4792,7 +4796,7 @@ nfsd42_encode_write_res(struct nfsd4_compoundres *resp,else {__be32 nfserr; *p++ =cpu_to_be32 (1 ); - nfserr =nfsd4_encode_stateid (resp->xdr, &write->cb_stateid); + nfserr =nfsd4_encode_stateid4 (resp->xdr, &write->cb_stateid);if (nfserr)return nfserr; }
@@ -4976,7 +4980,7 @@ nfsd4_encode_copy_notify(struct nfsd4_compoundres *resp, __be32 nfserr, *p++ =cpu_to_be32 (cn->cpn_nsec);/* cnr_stateid */ - nfserr =nfsd4_encode_stateid (xdr, &cn->cpn_cnr_stateid); + nfserr =nfsd4_encode_stateid4 (xdr, &cn->cpn_cnr_stateid);if (nfserr)return nfserr;
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 3/7 NFSD: Clean up nfsd4_encode_layoutreturn()
In reply to Sasha LevinFrom: Chuck Lever <[email protected]>
[ Upstream commit 85dbc978b33be6f5e2e06e34b5219d730d5f9aa4 ]
Adopt the use of conventional XDR utility functions. Restructure the encoder to better align with the XDR definition of the result.
Stable-dep-of: ca94ba361720 ("nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke")
--- fs/nfsd/nfs4layouts.c | 6 +++--- fs/nfsd/nfs4xdr.c | 12 ++++++------ fs/nfsd/xdr4.h | 2 +- 3 files changed, 10 insertions(+), 10 deletions(-)
fs/nfsd/nfs4layouts.c +3 -3
@@ -512,11 +512,11 @@ nfsd4_return_file_layouts(struct svc_rqst *rqstp,if (!list_empty (&ls->ls_layouts)) {if (found)nfs4_inc_and_copy_stateid (&lrp->lr_sid, &ls->ls_stid); - lrp->lrs_present =1 ; + lrp->lrs_present = true; }else {trace_nfsd_layoutstate_unhash (&ls->ls_stid.sc_stateid);nfs4_unhash_stid (&ls->ls_stid); - lrp->lrs_present =0 ; + lrp->lrs_present = false; }spin_unlock (&ls->ls_lock);
@@ -536,7 +536,7 @@ nfsd4_return_client_layouts(struct svc_rqst *rqstp,struct nfs4_layout *lp, *t;LIST_HEAD (reaplist); - lrp->lrs_present =0 ; + lrp->lrs_present = false;spin_lock (&clp->cl_lock);list_for_each_entry_safe (ls, n, &clp->cl_lo_states, ls_perclnt) {
fs/nfsd/nfs4xdr.c +6 -6
@@ -4770,15 +4770,15 @@ nfsd4_encode_layoutreturn(struct nfsd4_compoundres *resp, __be32 nfserr, {struct nfsd4_layoutreturn *lrp = &u->layoutreturn;struct xdr_stream *xdr = resp->xdr; -__be32 *p; - p =xdr_reserve_space (xdr,4 ); -if (!p) -return nfserr_resource; - *p++ =cpu_to_be32 (lrp->lrs_present); +/* lrs_present */ + nfserr =nfsd4_encode_bool (xdr, lrp->lrs_present); +if (nfserr != nfs_ok) +return nfserr;if (lrp->lrs_present) +/* lrs_stateid */ return nfsd4_encode_stateid4 (xdr, &lrp->lr_sid); -return 0 ; +return nfs_ok; }#endif /* CONFIG_NFSD_PNFS */
fs/nfsd/xdr4.h +1 -1
@@ -653,7 +653,7 @@ struct nfsd4_layoutreturn {u32 lrf_body_len;/* request */ void *lrf_body;/* request */ stateid_t lr_sid;/* request/response */ -u32 lrs_present;/* response */ +bool lrs_present;/* response */ };struct nfsd4_fallocate {
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 4/7 nfsd: don't call functions with side-effecting inside WARN_ON()
In reply to Sasha LevinFrom: NeilBrown <[email protected]>
[ Upstream commit c6540026dfe68b2cc6b6891404f8efa7c40e53e9 ]
Code like:
WARN_ON(foo())
looks like an assertion and might not be expected to have any side effects.
When testing if a function with side-effects fails a construct like
if (foo())
WARN_ON(1);makes the intent more obvious.
nfsd has several WARN_ON calls where the test has side effects, so it would be good to change them. These cases don't really need the WARN_ON. They have never failed in 8 years of usage so let's just remove the WARN_ON wrapper.
Stable-dep-of: ca94ba361720 ("nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke")
--- fs/nfsd/nfs4state.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-)
fs/nfsd/nfs4state.c +5 -5
@@ -1605,7 +1605,7 @@ static void release_open_stateid_locks(struct nfs4_ol_stateid *open_stp,while (!list_empty (&open_stp->st_locks)) { stp =list_entry (open_stp->st_locks.next, struct nfs4_ol_stateid, st_locks); -WARN_ON (!unhash_lock_stateid (stp)); +unhash_lock_stateid (stp);put_ol_stateid_locked (stp, reaplist); } }
@@ -2244,7 +2244,7 @@ __destroy_client(struct nfs4_client *clp)spin_lock (&state_lock);while (!list_empty (&clp->cl_delegations)) { dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt); -WARN_ON (!unhash_delegation_locked (dp)); +unhash_delegation_locked (dp);list_add (&dp->dl_recall_lru, &reaplist); }spin_unlock (&state_lock);
@@ -6250,7 +6250,7 @@ nfs4_laundromat(struct nfsd_net *nn) dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);if (!state_expired (<, dp->dl_time))break ; -WARN_ON (!unhash_delegation_locked (dp)); +unhash_delegation_locked (dp);list_add (&dp->dl_recall_lru, &reaplist); }spin_unlock (&state_lock);
@@ -8065,7 +8065,7 @@ nfsd4_release_lockowner(struct svc_rqst *rqstp, stp =list_first_entry (&lo->lo_owner.so_stateids, struct nfs4_ol_stateid, st_perstateowner); -WARN_ON (!unhash_lock_stateid (stp)); +unhash_lock_stateid (stp);put_ol_stateid_locked (stp, &reaplist); }spin_unlock (&clp->cl_lock);
@@ -8380,7 +8380,7 @@ nfs4_state_shutdown_net(struct net *net)spin_lock (&state_lock);list_for_each_safe (pos, next, &nn->del_recall_lru) { dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); -WARN_ON (!unhash_delegation_locked (dp)); +unhash_delegation_locked (dp);list_add (&dp->dl_recall_lru, &reaplist); }spin_unlock (&state_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 5/7 nfsd: avoid race after unhash_delegation_locked()
In reply to Sasha LevinFrom: NeilBrown <[email protected]>
[ Upstream commit 83e733161fde43e2f99cefa68e369944460fce39 ]
NFS4_CLOSED_DELEG_STID and NFS4_REVOKED_DELEG_STID are similar in purpose.
REVOKED is used for NFSv4.1 states which have been revoked because the lease has expired. CLOSED is used in other cases.
The difference has two practical effects.
1/ REVOKED states are on the ->cl_revoked list
2/ REVOKED states result in nfserr_deleg_revoked from
nfsd4_verify_open_stid() and nfsd4_validate_stateid while CLOSED states result in nfserr_bad_stid.
Currently a state that is being revoked is first set to "CLOSED" in unhash_delegation_locked(), then possibly to "REVOKED" in revoke_delegation(), at which point it is added to the cl_revoked list.
It is possible that a stateid test could see the CLOSED state which really should be REVOKED, and so return the wrong error code. So it is safest to remove this window of inconsistency.
With this patch, unhash_delegation_locked() always sets the state correctly, and revoke_delegation() no longer changes the state.
Also remove a redundant test on minorversion when NFS4_REVOKED_DELEG_STID is seen - it can only be seen when minorversion is non-zero.
Stable-dep-of: ca94ba361720 ("nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke")
--- fs/nfsd/nfs4state.c | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-)
fs/nfsd/nfs4state.c +10 -10
@@ -1333,7 +1333,7 @@ static bool delegation_hashed(struct nfs4_delegation *dp) }static bool -unhash_delegation_locked (struct nfs4_delegation *dp) +unhash_delegation_locked (struct nfs4_delegation *dp,unsigned char type) {struct nfs4_file *fp = dp->dl_stid.sc_file;
@@ -1342,7 +1342,9 @@ unhash_delegation_locked(struct nfs4_delegation *dp)if (!delegation_hashed (dp))return false; - dp->dl_stid.sc_type =NFS4_CLOSED_DELEG_STID ; +if (dp->dl_stid.sc_client->cl_minorversion ==0 ) + type =NFS4_CLOSED_DELEG_STID ; + dp->dl_stid.sc_type = type;/* Ensure that deleg break won't try to requeue it */ ++dp->dl_time;spin_lock (&fp->fi_lock);
@@ -1358,7 +1360,7 @@ static void destroy_delegation(struct nfs4_delegation *dp)bool unhashed;spin_lock (&state_lock); - unhashed =unhash_delegation_locked (dp); + unhashed =unhash_delegation_locked (dp,NFS4_CLOSED_DELEG_STID );spin_unlock (&state_lock);if (unhashed)destroy_unhashed_deleg (dp);
@@ -1372,9 +1374,8 @@ static void revoke_delegation(struct nfs4_delegation *dp)trace_nfsd_stid_revoke (&dp->dl_stid); -if (clp->cl_minorversion) { +if (dp->dl_stid.sc_type ==NFS4_REVOKED_DELEG_STID ) {spin_lock (&clp->cl_lock); - dp->dl_stid.sc_type =NFS4_REVOKED_DELEG_STID ;refcount_inc (&dp->dl_stid.sc_count);list_add (&dp->dl_recall_lru, &clp->cl_revoked);spin_unlock (&clp->cl_lock);
@@ -2244,7 +2245,7 @@ __destroy_client(struct nfs4_client *clp)spin_lock (&state_lock);while (!list_empty (&clp->cl_delegations)) { dp = list_entry(clp->cl_delegations.next, struct nfs4_delegation, dl_perclnt); -unhash_delegation_locked (dp); +unhash_delegation_locked (dp,NFS4_CLOSED_DELEG_STID );list_add (&dp->dl_recall_lru, &reaplist); }spin_unlock (&state_lock);
@@ -5179,8 +5180,7 @@ nfs4_check_deleg(struct nfs4_client *cl, struct nfsd4_open *open, goto out;if (deleg->dl_stid.sc_type ==NFS4_REVOKED_DELEG_STID ) {nfs4_put_stid (&deleg->dl_stid); -if (cl->cl_minorversion) - status = nfserr_deleg_revoked; + status = nfserr_deleg_revoked; goto out; } flags =share_access_to_flags (open->op_share_access);
@@ -6250,7 +6250,7 @@ nfs4_laundromat(struct nfsd_net *nn) dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru);if (!state_expired (<, dp->dl_time))break ; -unhash_delegation_locked (dp); +unhash_delegation_locked (dp,NFS4_REVOKED_DELEG_STID );list_add (&dp->dl_recall_lru, &reaplist); }spin_unlock (&state_lock);
@@ -8380,7 +8380,7 @@ nfs4_state_shutdown_net(struct net *net)spin_lock (&state_lock);list_for_each_safe (pos, next, &nn->del_recall_lru) { dp = list_entry (pos, struct nfs4_delegation, dl_recall_lru); -unhash_delegation_locked (dp); +unhash_delegation_locked (dp,NFS4_CLOSED_DELEG_STID );list_add (&dp->dl_recall_lru, &reaplist); }spin_unlock (&state_lock);
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 6/7 nfsd: split sc_status out of sc_type
In reply to Sasha LevinFrom: NeilBrown <[email protected]>
[ Upstream commit 3f29cc82a84c23cfd12b903029dd26002ca825f5 ]
sc_type identifies the type of a state - open, lock, deleg, layout - and also the status of a state - closed or revoked.
This is a bit untidy and could get worse when "admin-revoked" states are added. So clean it up.
With this patch, the type is now all that is stored in sc_type. This is zero when the state is first added to ->cl_stateids (causing it to be ignored), and is then set appropriately once it is fully initialised. It is set under ->cl_lock to ensure atomicity w.r.t lookup. It is now never cleared.
sc_type is still a bit-set even though at most one bit is set. This allows lookup functions to be given a bitmap of acceptable types.
sc_type is now an unsigned short rather than char. There is no value in restricting to just 8 bits.
All the constants now start SC_TYPE_ matching the field in which they are stored. Keeping the existing names and ensuring clear separation from non-type flags would have required something like NFS4_STID_TYPE_CLOSED which is cumbersome. The "NFS4" prefix is redundant was they only appear in NFS4 code, so remove that and change STID to SC to match the field.
The status is stored in a separate unsigned short named "sc_status". It has two flags: SC_STATUS_CLOSED and SC_STATUS_REVOKED.
CLOSED combines NFS4_CLOSED_STID, NFS4_CLOSED_DELEG_STID, and is used for SC_TYPE_LOCK and SC_TYPE_LAYOUT instead of setting the sc_type to zero. These flags are only ever set, never cleared.
For deleg stateids they are set under the global state_lock. For open and lock stateids they are set under ->cl_lock.
For layout stateids they are set under ->ls_lock
nfs4_unhash_stid() has been removed, and we never set sc_type = 0. This was only used for LOCK and LAYOUT stids and they now use SC_STATUS_CLOSED.
Also TRACE_DEFINE_NUM() calls for the various STID #define have been removed because these things are not enums, and so that call is incorrect.
Stable-dep-of: ca94ba361720 ("nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke")
--- fs/nfsd/nfs4layouts.c | 14 +-- fs/nfsd/nfs4state.c | 207 +++++++++++++++++++++--------------------- fs/nfsd/state.h | 40 +++++--- fs/nfsd/trace.h | 31 +++---- 4 files changed, 151 insertions(+), 141 deletions(-)
fs/nfsd/nfs4layouts.c +7 -7
@@ -235,7 +235,7 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate,nfsd4_init_cb (&ls->ls_recall, clp, &nfsd4_cb_layout_ops,NFSPROC4_CLNT_CB_LAYOUT ); -if (parent->sc_type ==NFS4_DELEG_STID ) +if (parent->sc_type ==SC_TYPE_DELEG ) ls->ls_file =nfsd_file_get (fp->fi_deleg_file);else ls->ls_file =find_any_file (fp);
@@ -247,7 +247,7 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate, }spin_lock (&clp->cl_lock); - stp->sc_type =NFS4_LAYOUT_STID ; + stp->sc_type =SC_TYPE_LAYOUT ;list_add (&ls->ls_perclnt, &clp->cl_lo_states);spin_unlock (&clp->cl_lock);
@@ -266,13 +266,13 @@ nfsd4_preprocess_layout_stateid(struct svc_rqst *rqstp, {struct nfs4_layout_stateid *ls;struct nfs4_stid *stid; -unsigned char typemask =NFS4_LAYOUT_STID ; +unsigned short typemask =SC_TYPE_LAYOUT ;__be32 status;if (create) - typemask |= (NFS4_OPEN_STID |NFS4_LOCK_STID |NFS4_DELEG_STID ); + typemask |= (SC_TYPE_OPEN |SC_TYPE_LOCK |SC_TYPE_DELEG ); - status =nfsd4_lookup_stateid (cstate, stateid, typemask, &stid, + status =nfsd4_lookup_stateid (cstate, stateid, typemask,0 , &stid,net_generic (SVC_NET (rqstp), nfsd_net_id));if (status) goto out;
@@ -283,7 +283,7 @@ nfsd4_preprocess_layout_stateid(struct svc_rqst *rqstp, goto out_put_stid; } -if (stid->sc_type !=NFS4_LAYOUT_STID ) { +if (stid->sc_type !=SC_TYPE_LAYOUT ) { ls =nfsd4_alloc_layout_stateid (cstate, stid, layout_type);nfs4_put_stid (stid);
@@ -515,7 +515,7 @@ nfsd4_return_file_layouts(struct svc_rqst *rqstp, lrp->lrs_present = true; }else {trace_nfsd_layoutstate_unhash (&ls->ls_stid.sc_stateid); -nfs4_unhash_stid (&ls->ls_stid); + ls->ls_stid.sc_status |=SC_STATUS_CLOSED ; lrp->lrs_present = false; }spin_unlock (&ls->ls_lock);
fs/nfsd/nfs4state.c +102 -105
509 lines not on this page · raw message
fs/nfsd/state.h +28 -12
@@ -88,17 +88,33 @@ struct nfsd4_callback_ops {*/ struct nfs4_stid {refcount_t sc_count; -#define NFS4_OPEN_STID 1 -#define NFS4_LOCK_STID 2 -#define NFS4_DELEG_STID 4 -/* For an open stateid kept around *only* to process close replays: */ -#define NFS4_CLOSED_STID 8 + +/* A new stateid is added to the cl_stateids idr early before it +* is fully initialised. Its sc_type is then zero. After +* initialisation the sc_type it set under cl_lock, and then +* never changes. +*/ +#define SC_TYPE_OPEN BIT(0) +#define SC_TYPE_LOCK BIT(1) +#define SC_TYPE_DELEG BIT(2) +#define SC_TYPE_LAYOUT BIT(3) +unsigned short sc_type; + +/* state_lock protects sc_status for delegation stateids. +* ->cl_lock protects sc_status for open and lock stateids. +* ->st_mutex also protect sc_status for open stateids. +* ->ls_lock protects sc_status for layout stateids. +*/ +/* +* For an open stateid kept around *only* to process close replays. +* For deleg stateid, kept in idr until last reference is dropped. +*/ +#define SC_STATUS_CLOSED BIT(0)/* For a deleg stateid kept around only to process free_stateid's: */ -#define NFS4_REVOKED_DELEG_STID 16 -#define NFS4_CLOSED_DELEG_STID 32 -#define NFS4_LAYOUT_STID 64 +#define SC_STATUS_REVOKED BIT(1) +unsigned short sc_status; +struct list_head sc_cp_list; -unsigned char sc_type;stateid_t sc_stateid;spinlock_t sc_lock;struct nfs4_client *sc_client;
@@ -681,15 +697,15 @@ extern __be32 nfs4_preprocess_stateid_op(struct svc_rqst *rqstp, stateid_t *stateid, int flags, struct nfsd_file **filp, struct nfs4_stid **cstid);__be32 nfsd4_lookup_stateid (struct nfsd4_compound_state *cstate, -stateid_t *stateid,unsigned char typemask, -struct nfs4_stid **s,struct nfsd_net *nn); +stateid_t *stateid,unsigned short typemask, +unsigned short statusmask, +struct nfs4_stid **s,struct nfsd_net *nn);struct nfs4_stid *nfs4_alloc_stid (struct nfs4_client *cl,struct kmem_cache *slab,void (*sc_free)(struct nfs4_stid *));int nfs4_init_copy_state (struct nfsd_net *nn,struct nfsd4_copy *copy);void nfs4_free_copy_state (struct nfsd4_copy *copy);struct nfs4_cpntf_state *nfs4_alloc_init_cpntf_state (struct nfsd_net *nn,struct nfs4_stid *p_stid); -void nfs4_unhash_stid (struct nfs4_stid *s);void nfs4_put_stid (struct nfs4_stid *s);void nfs4_inc_and_copy_stateid (stateid_t *dst,struct nfs4_stid *stid);void nfs4_remove_reclaim_record (struct nfs4_client_reclaim *,struct nfsd_net *);
fs/nfsd/trace.h +14 -17
@@ -641,23 +641,17 @@ DEFINE_EVENT(nfsd_stateseqid_class, nfsd_##name, \DEFINE_STATESEQID_EVENT (preprocess);DEFINE_STATESEQID_EVENT (open_confirm); -TRACE_DEFINE_ENUM (NFS4_OPEN_STID ); -TRACE_DEFINE_ENUM (NFS4_LOCK_STID ); -TRACE_DEFINE_ENUM (NFS4_DELEG_STID ); -TRACE_DEFINE_ENUM (NFS4_CLOSED_STID ); -TRACE_DEFINE_ENUM (NFS4_REVOKED_DELEG_STID ); -TRACE_DEFINE_ENUM (NFS4_CLOSED_DELEG_STID ); -TRACE_DEFINE_ENUM (NFS4_LAYOUT_STID ); -#define show_stid_type (x) \ __print_flags(x, "|", \ - { NFS4_OPEN_STID, "OPEN" }, \ - { NFS4_LOCK_STID, "LOCK" }, \ - { NFS4_DELEG_STID, "DELEG" }, \ - { NFS4_CLOSED_STID, "CLOSED" }, \ - { NFS4_REVOKED_DELEG_STID, "REVOKED" }, \ - { NFS4_CLOSED_DELEG_STID, "CLOSED_DELEG" }, \ - { NFS4_LAYOUT_STID, "LAYOUT" }) + { SC_TYPE_OPEN, "OPEN" }, \ + { SC_TYPE_LOCK, "LOCK" }, \ + { SC_TYPE_DELEG, "DELEG" }, \ + { SC_TYPE_LAYOUT, "LAYOUT" }) + +#define show_stid_status (x) \ + __print_flags(x, "|", \ + { SC_STATUS_CLOSED, "CLOSED" }, \ + { SC_STATUS_REVOKED, "REVOKED" }) \ DECLARE_EVENT_CLASS(nfsd_stid_class, TP_PROTO(
@@ -666,6 +660,7 @@ DECLARE_EVENT_CLASS(nfsd_stid_class,TP_ARGS (stid),TP_STRUCT__entry ( __field(unsigned long, sc_type) +__field (unsigned long ,sc_status )__field (int, sc_count)__field (u32, cl_boot) __field(u32, cl_id)
@@ -676,16 +671,18 @@ DECLARE_EVENT_CLASS(nfsd_stid_class,const stateid_t *stp = &stid->sc_stateid; __entry->sc_type = stid->sc_type; + __entry->sc_status = stid->sc_status; __entry->sc_count =refcount_read (&stid->sc_count); __entry->cl_boot = stp->si_opaque.so_clid.cl_boot; __entry->cl_id = stp->si_opaque.so_clid.cl_id; __entry->si_id = stp->si_opaque.so_id; __entry->si_generation = stp->si_generation; ), -TP_printk ("client %08x:%08x stateid %08x:%08x ref=%d type=%s" , +TP_printk ("client %08x:%08x stateid %08x:%08x ref=%d type=%s state=%s" , __entry->cl_boot, __entry->cl_id, __entry->si_id, __entry->si_generation, - __entry->sc_count,show_stid_type (__entry->sc_type) + __entry->sc_count,show_stid_type (__entry->sc_type), +show_stid_status (__entry->sc_status) ) );
Signature · 1 line
2.53.0
Sasha Levin
maintainerPATCH 7/7 nfsd: fix BUG_ON in nfsd4_alloc_layout_stateid on racing delegation revoke
In reply to Sasha LevinFrom: Jeff Layton <[email protected]>
[ Upstream commit ca94ba36172046be6a694a7986f6931e47ed4d51 ]
nfsd4_alloc_layout_stateid reads fp->fi_deleg_file without holding fi_lock when the parent stateid is a delegation. A concurrent delegation revoke via the laundromat can clear fi_deleg_file under fi_lock, causing nfsd_file_get() to return NULL and triggering the BUG_ON.
This race is client-reachable: two NFS clients can trigger it by having one hold a delegation while another opens the same file to force a recall. When the first client doesn't respond to the recall, the laundromat revokes it. A concurrent LAYOUTGET from any client using the delegation stateid hits the race window.
Fix this by taking fi_lock around the fi_deleg_file read in the SC_TYPE_DELEG path, matching the locking discipline of the find_any_file() arm, and replacing the BUG_ON with a graceful error return that cleans up the partially-initialized layout stateid.
--- fs/nfsd/nfs4layouts.c | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-)
fs/nfsd/nfs4layouts.c +9 -3
@@ -235,11 +235,17 @@ nfsd4_alloc_layout_stateid(struct nfsd4_compound_state *cstate,nfsd4_init_cb (&ls->ls_recall, clp, &nfsd4_cb_layout_ops,NFSPROC4_CLNT_CB_LAYOUT ); -if (parent->sc_type ==SC_TYPE_DELEG ) +if (parent->sc_type ==SC_TYPE_DELEG ) { +spin_lock (&fp->fi_lock); ls->ls_file =nfsd_file_get (fp->fi_deleg_file); -else +spin_unlock (&fp->fi_lock); + }else { ls->ls_file =find_any_file (fp); -BUG_ON (!ls->ls_file); + } +if (!ls->ls_file) { +nfs4_put_stid (stp); +return NULL ; + }if (nfsd4_layout_setlease (ls)) {nfs4_put_stid (stp);
Signature · 1 line
2.53.0