* Re: [PATCH 1/2] nfsd: ensure SEQUENCE replay sends a valid reply.
2025-10-14 0:04 ` [PATCH 1/2] nfsd: ensure SEQUENCE replay sends a valid reply NeilBrown
@ 2025-10-14 13:44 ` Jeff Layton
2025-10-14 13:57 ` Chuck Lever
2025-10-14 14:57 ` Olga Kornievskaia
2 siblings, 0 replies; 9+ messages in thread
From: Jeff Layton @ 2025-10-14 13:44 UTC (permalink / raw)
To: NeilBrown, Chuck Lever; +Cc: Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
On Tue, 2025-10-14 at 11:04 +1100, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
>
> nfsd4_enc_sequence_replay() uses nfsd4_encode_operation() to encode a
> new SEQUENCE reply when replaying a request from the slot cache - only
> ops after the SEQUENCE are replay from the cache in ->sl_data.
>
> However it does this in nfsd4_replay_cache_entry() which is called
> *before* nfsd4_sequence() has filled in reply fields.
>
> This means that in the replayed SEQUENCE reply:
> maxslots will be whatever the client sent
> target_maxslots will be -1 (assuming init to zero, and
> nfsd4_encode_sequence() subtracts 1)
> status_flags will be zero
>
> which might mislead the client.
>
> This patch moves the setup of the reply to *before*
> nfsd4_replay_cache_entry() is called. Only one of the updated fields is
> used after this point - maxslots. So that field is copied to
> client_maxslots so that can be used as needed.
>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
> fs/nfsd/nfs4state.c | 41 +++++++++++++++++++++++------------------
> 1 file changed, 23 insertions(+), 18 deletions(-)
>
> diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> index c9053ef4d79f..1c01836e8507 100644
> --- a/fs/nfsd/nfs4state.c
> +++ b/fs/nfsd/nfs4state.c
> @@ -4360,6 +4360,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> struct nfs4_client *clp;
> struct nfsd4_slot *slot;
> struct nfsd4_conn *conn;
> + u32 client_maxslots;
> __be32 status;
> int buflen;
> struct net *net = SVC_NET(rqstp);
> @@ -4398,6 +4399,27 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> dprintk("%s: slotid %d\n", __func__, seq->slotid);
>
> trace_nfsd_slot_seqid_sequence(clp, seq, slot);
> +
> + /* prepare reply so that it is ready for nfsd4_replay_cache_entry() */
> + client_maxslots = seq->maxslots;
> + seq->maxslots = max(session->se_target_maxslots, client_maxslots);
> + seq->target_maxslots = session->se_target_maxslots;
> +
> + switch (clp->cl_cb_state) {
> + case NFSD4_CB_DOWN:
> + seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> + break;
> + case NFSD4_CB_FAULT:
> + seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> + break;
> + default:
> + seq->status_flags = 0;
> + }
> + if (!list_empty(&clp->cl_revoked))
> + seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> + if (atomic_read(&clp->cl_admin_revoked))
> + seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> +
> status = check_slot_seqid(seq->seqid, slot->sl_seqid, slot->sl_flags);
> if (status == nfserr_replay_cache) {
> status = nfserr_seq_misordered;
> @@ -4425,7 +4447,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>
> if (session->se_target_maxslots < session->se_fchannel.maxreqs &&
> slot->sl_generation == session->se_slot_gen &&
> - seq->maxslots <= session->se_target_maxslots)
> + client_maxslots <= session->se_target_maxslots)
> /* Client acknowledged our reduce maxreqs */
> free_session_slots(session, session->se_target_maxslots);
>
> @@ -4495,23 +4517,6 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> }
>
> out:
> - seq->maxslots = max(session->se_target_maxslots, seq->maxslots);
> - seq->target_maxslots = session->se_target_maxslots;
> -
> - switch (clp->cl_cb_state) {
> - case NFSD4_CB_DOWN:
> - seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> - break;
> - case NFSD4_CB_FAULT:
> - seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> - break;
> - default:
> - seq->status_flags = 0;
> - }
> - if (!list_empty(&clp->cl_revoked))
> - seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> - if (atomic_read(&clp->cl_admin_revoked))
> - seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> trace_nfsd_seq4_status(rqstp, seq);
> out_no_session:
> if (conn)
This does seem like the right thing to do.
Reviewed-by: Jeff Layton <jlayton@kernel.org>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 1/2] nfsd: ensure SEQUENCE replay sends a valid reply.
2025-10-14 0:04 ` [PATCH 1/2] nfsd: ensure SEQUENCE replay sends a valid reply NeilBrown
2025-10-14 13:44 ` Jeff Layton
@ 2025-10-14 13:57 ` Chuck Lever
2025-10-14 14:57 ` Olga Kornievskaia
2 siblings, 0 replies; 9+ messages in thread
From: Chuck Lever @ 2025-10-14 13:57 UTC (permalink / raw)
To: NeilBrown, Jeff Layton; +Cc: Olga Kornievskaia, Dai Ngo, Tom Talpey, linux-nfs
On 10/13/25 8:04 PM, NeilBrown wrote:
> From: NeilBrown <neil@brown.name>
>
> nfsd4_enc_sequence_replay() uses nfsd4_encode_operation() to encode a
> new SEQUENCE reply when replaying a request from the slot cache - only
> ops after the SEQUENCE are replay from the cache in ->sl_data.
s/replay /replayed /
You might consider moving reply construction to a helper, where it
can be more extensively documented without adding more clutter to
nfsd4_sequence().
> However it does this in nfsd4_replay_cache_entry() which is called
> *before* nfsd4_sequence() has filled in reply fields.
>
> This means that in the replayed SEQUENCE reply:
> maxslots will be whatever the client sent
> target_maxslots will be -1 (assuming init to zero, and
> nfsd4_encode_sequence() subtracts 1)
> status_flags will be zero
>
> which might mislead the client.
>
> This patch moves the setup of the reply to *before*
> nfsd4_replay_cache_entry() is called. Only one of the updated fields is
> used after this point - maxslots. So that field is copied to
> client_maxslots so that can be used as needed.
>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
> fs/nfsd/nfs4state.c | 41 +++++++++++++++++++++++------------------
> 1 file changed, 23 insertions(+), 18 deletions(-)
>
> diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> index c9053ef4d79f..1c01836e8507 100644
> --- a/fs/nfsd/nfs4state.c
> +++ b/fs/nfsd/nfs4state.c
> @@ -4360,6 +4360,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> struct nfs4_client *clp;
> struct nfsd4_slot *slot;
> struct nfsd4_conn *conn;
> + u32 client_maxslots;
> __be32 status;
> int buflen;
> struct net *net = SVC_NET(rqstp);
> @@ -4398,6 +4399,27 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> dprintk("%s: slotid %d\n", __func__, seq->slotid);
>
> trace_nfsd_slot_seqid_sequence(clp, seq, slot);
> +
> + /* prepare reply so that it is ready for nfsd4_replay_cache_entry() */
> + client_maxslots = seq->maxslots;
> + seq->maxslots = max(session->se_target_maxslots, client_maxslots);
> + seq->target_maxslots = session->se_target_maxslots;
> +
> + switch (clp->cl_cb_state) {
> + case NFSD4_CB_DOWN:
> + seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> + break;
> + case NFSD4_CB_FAULT:
> + seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> + break;
> + default:
> + seq->status_flags = 0;
> + }
> + if (!list_empty(&clp->cl_revoked))
> + seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> + if (atomic_read(&clp->cl_admin_revoked))
> + seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> +
> status = check_slot_seqid(seq->seqid, slot->sl_seqid, slot->sl_flags);
> if (status == nfserr_replay_cache) {
> status = nfserr_seq_misordered;
> @@ -4425,7 +4447,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>
> if (session->se_target_maxslots < session->se_fchannel.maxreqs &&
> slot->sl_generation == session->se_slot_gen &&
> - seq->maxslots <= session->se_target_maxslots)
> + client_maxslots <= session->se_target_maxslots)
> /* Client acknowledged our reduce maxreqs */
> free_session_slots(session, session->se_target_maxslots);
>
> @@ -4495,23 +4517,6 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> }
>
> out:
> - seq->maxslots = max(session->se_target_maxslots, seq->maxslots);
> - seq->target_maxslots = session->se_target_maxslots;
> -
> - switch (clp->cl_cb_state) {
> - case NFSD4_CB_DOWN:
> - seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> - break;
> - case NFSD4_CB_FAULT:
> - seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> - break;
> - default:
> - seq->status_flags = 0;
> - }
> - if (!list_empty(&clp->cl_revoked))
> - seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> - if (atomic_read(&clp->cl_admin_revoked))
> - seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> trace_nfsd_seq4_status(rqstp, seq);
> out_no_session:
> if (conn)
--
Chuck Lever
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 1/2] nfsd: ensure SEQUENCE replay sends a valid reply.
2025-10-14 0:04 ` [PATCH 1/2] nfsd: ensure SEQUENCE replay sends a valid reply NeilBrown
2025-10-14 13:44 ` Jeff Layton
2025-10-14 13:57 ` Chuck Lever
@ 2025-10-14 14:57 ` Olga Kornievskaia
2025-10-14 22:49 ` NeilBrown
2 siblings, 1 reply; 9+ messages in thread
From: Olga Kornievskaia @ 2025-10-14 14:57 UTC (permalink / raw)
To: NeilBrown
Cc: Chuck Lever, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey,
linux-nfs
On Mon, Oct 13, 2025 at 8:06 PM NeilBrown <neilb@ownmail.net> wrote:
>
> From: NeilBrown <neil@brown.name>
>
> nfsd4_enc_sequence_replay() uses nfsd4_encode_operation() to encode a
> new SEQUENCE reply when replaying a request from the slot cache - only
> ops after the SEQUENCE are replay from the cache in ->sl_data.
>
> However it does this in nfsd4_replay_cache_entry() which is called
> *before* nfsd4_sequence() has filled in reply fields.
>
> This means that in the replayed SEQUENCE reply:
> maxslots will be whatever the client sent
> target_maxslots will be -1 (assuming init to zero, and
> nfsd4_encode_sequence() subtracts 1)
> status_flags will be zero
>
> which might mislead the client.
This also fixes the problem I described in my proposed patch. I would
have liked to see a bit more detail mentioning that it leads to the
client shrinking its slot table and then a hung client due to the
server's SEQ_MISORDERED error. I think having details in the commit
message might be useful for later connecting the symptoms to this
patch.
Tested-by: Olga Kornievskaia <okorniev@redhat.com>
> This patch moves the setup of the reply to *before*
> nfsd4_replay_cache_entry() is called. Only one of the updated fields is
> used after this point - maxslots. So that field is copied to
> client_maxslots so that can be used as needed.
>
> Signed-off-by: NeilBrown <neil@brown.name>
> ---
> fs/nfsd/nfs4state.c | 41 +++++++++++++++++++++++------------------
> 1 file changed, 23 insertions(+), 18 deletions(-)
>
> diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> index c9053ef4d79f..1c01836e8507 100644
> --- a/fs/nfsd/nfs4state.c
> +++ b/fs/nfsd/nfs4state.c
> @@ -4360,6 +4360,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> struct nfs4_client *clp;
> struct nfsd4_slot *slot;
> struct nfsd4_conn *conn;
> + u32 client_maxslots;
> __be32 status;
> int buflen;
> struct net *net = SVC_NET(rqstp);
> @@ -4398,6 +4399,27 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> dprintk("%s: slotid %d\n", __func__, seq->slotid);
>
> trace_nfsd_slot_seqid_sequence(clp, seq, slot);
> +
> + /* prepare reply so that it is ready for nfsd4_replay_cache_entry() */
> + client_maxslots = seq->maxslots;
> + seq->maxslots = max(session->se_target_maxslots, client_maxslots);
> + seq->target_maxslots = session->se_target_maxslots;
> +
> + switch (clp->cl_cb_state) {
> + case NFSD4_CB_DOWN:
> + seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> + break;
> + case NFSD4_CB_FAULT:
> + seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> + break;
> + default:
> + seq->status_flags = 0;
> + }
> + if (!list_empty(&clp->cl_revoked))
> + seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> + if (atomic_read(&clp->cl_admin_revoked))
> + seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> +
> status = check_slot_seqid(seq->seqid, slot->sl_seqid, slot->sl_flags);
> if (status == nfserr_replay_cache) {
> status = nfserr_seq_misordered;
> @@ -4425,7 +4447,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
>
> if (session->se_target_maxslots < session->se_fchannel.maxreqs &&
> slot->sl_generation == session->se_slot_gen &&
> - seq->maxslots <= session->se_target_maxslots)
> + client_maxslots <= session->se_target_maxslots)
> /* Client acknowledged our reduce maxreqs */
> free_session_slots(session, session->se_target_maxslots);
>
> @@ -4495,23 +4517,6 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> }
>
> out:
> - seq->maxslots = max(session->se_target_maxslots, seq->maxslots);
> - seq->target_maxslots = session->se_target_maxslots;
> -
> - switch (clp->cl_cb_state) {
> - case NFSD4_CB_DOWN:
> - seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> - break;
> - case NFSD4_CB_FAULT:
> - seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> - break;
> - default:
> - seq->status_flags = 0;
> - }
> - if (!list_empty(&clp->cl_revoked))
> - seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> - if (atomic_read(&clp->cl_admin_revoked))
> - seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> trace_nfsd_seq4_status(rqstp, seq);
> out_no_session:
> if (conn)
> --
> 2.50.0.107.gf914562f5916.dirty
>
>
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH 1/2] nfsd: ensure SEQUENCE replay sends a valid reply.
2025-10-14 14:57 ` Olga Kornievskaia
@ 2025-10-14 22:49 ` NeilBrown
0 siblings, 0 replies; 9+ messages in thread
From: NeilBrown @ 2025-10-14 22:49 UTC (permalink / raw)
To: Olga Kornievskaia
Cc: Chuck Lever, Jeff Layton, Olga Kornievskaia, Dai Ngo, Tom Talpey,
linux-nfs
On Wed, 15 Oct 2025, Olga Kornievskaia wrote:
> On Mon, Oct 13, 2025 at 8:06 PM NeilBrown <neilb@ownmail.net> wrote:
> >
> > From: NeilBrown <neil@brown.name>
> >
> > nfsd4_enc_sequence_replay() uses nfsd4_encode_operation() to encode a
> > new SEQUENCE reply when replaying a request from the slot cache - only
> > ops after the SEQUENCE are replay from the cache in ->sl_data.
> >
> > However it does this in nfsd4_replay_cache_entry() which is called
> > *before* nfsd4_sequence() has filled in reply fields.
> >
> > This means that in the replayed SEQUENCE reply:
> > maxslots will be whatever the client sent
> > target_maxslots will be -1 (assuming init to zero, and
> > nfsd4_encode_sequence() subtracts 1)
> > status_flags will be zero
> >
> > which might mislead the client.
>
> This also fixes the problem I described in my proposed patch. I would
> have liked to see a bit more detail mentioning that it leads to the
> client shrinking its slot table and then a hung client due to the
> server's SEQ_MISORDERED error. I think having details in the commit
> message might be useful for later connecting the symptoms to this
> patch.
I had seen that patch of yours but hadn't looked at it properly yet. I
does indeed address the same problem. I'll add relevant parts of your
commit message to me next posting.
>
> Tested-by: Olga Kornievskaia <okorniev@redhat.com>
Thanks!
NeilBrown
>
> > This patch moves the setup of the reply to *before*
> > nfsd4_replay_cache_entry() is called. Only one of the updated fields is
> > used after this point - maxslots. So that field is copied to
> > client_maxslots so that can be used as needed.
> >
> > Signed-off-by: NeilBrown <neil@brown.name>
> > ---
> > fs/nfsd/nfs4state.c | 41 +++++++++++++++++++++++------------------
> > 1 file changed, 23 insertions(+), 18 deletions(-)
> >
> > diff --git a/fs/nfsd/nfs4state.c b/fs/nfsd/nfs4state.c
> > index c9053ef4d79f..1c01836e8507 100644
> > --- a/fs/nfsd/nfs4state.c
> > +++ b/fs/nfsd/nfs4state.c
> > @@ -4360,6 +4360,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> > struct nfs4_client *clp;
> > struct nfsd4_slot *slot;
> > struct nfsd4_conn *conn;
> > + u32 client_maxslots;
> > __be32 status;
> > int buflen;
> > struct net *net = SVC_NET(rqstp);
> > @@ -4398,6 +4399,27 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> > dprintk("%s: slotid %d\n", __func__, seq->slotid);
> >
> > trace_nfsd_slot_seqid_sequence(clp, seq, slot);
> > +
> > + /* prepare reply so that it is ready for nfsd4_replay_cache_entry() */
> > + client_maxslots = seq->maxslots;
> > + seq->maxslots = max(session->se_target_maxslots, client_maxslots);
> > + seq->target_maxslots = session->se_target_maxslots;
> > +
> > + switch (clp->cl_cb_state) {
> > + case NFSD4_CB_DOWN:
> > + seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> > + break;
> > + case NFSD4_CB_FAULT:
> > + seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> > + break;
> > + default:
> > + seq->status_flags = 0;
> > + }
> > + if (!list_empty(&clp->cl_revoked))
> > + seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> > + if (atomic_read(&clp->cl_admin_revoked))
> > + seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> > +
> > status = check_slot_seqid(seq->seqid, slot->sl_seqid, slot->sl_flags);
> > if (status == nfserr_replay_cache) {
> > status = nfserr_seq_misordered;
> > @@ -4425,7 +4447,7 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> >
> > if (session->se_target_maxslots < session->se_fchannel.maxreqs &&
> > slot->sl_generation == session->se_slot_gen &&
> > - seq->maxslots <= session->se_target_maxslots)
> > + client_maxslots <= session->se_target_maxslots)
> > /* Client acknowledged our reduce maxreqs */
> > free_session_slots(session, session->se_target_maxslots);
> >
> > @@ -4495,23 +4517,6 @@ nfsd4_sequence(struct svc_rqst *rqstp, struct nfsd4_compound_state *cstate,
> > }
> >
> > out:
> > - seq->maxslots = max(session->se_target_maxslots, seq->maxslots);
> > - seq->target_maxslots = session->se_target_maxslots;
> > -
> > - switch (clp->cl_cb_state) {
> > - case NFSD4_CB_DOWN:
> > - seq->status_flags = SEQ4_STATUS_CB_PATH_DOWN;
> > - break;
> > - case NFSD4_CB_FAULT:
> > - seq->status_flags = SEQ4_STATUS_BACKCHANNEL_FAULT;
> > - break;
> > - default:
> > - seq->status_flags = 0;
> > - }
> > - if (!list_empty(&clp->cl_revoked))
> > - seq->status_flags |= SEQ4_STATUS_RECALLABLE_STATE_REVOKED;
> > - if (atomic_read(&clp->cl_admin_revoked))
> > - seq->status_flags |= SEQ4_STATUS_ADMIN_STATE_REVOKED;
> > trace_nfsd_seq4_status(rqstp, seq);
> > out_no_session:
> > if (conn)
> > --
> > 2.50.0.107.gf914562f5916.dirty
> >
> >
>
^ permalink raw reply [flat|nested] 9+ messages in thread