mirror of
https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git
synced 2026-08-31 08:15:07 -04:00
nfsd: fix netlink dumpit error handling for rpc_status_get
nfsd_genl_rpc_status_compose_msg() returns -ENOBUFS on nla_put failure
without calling genlmsg_cancel(), leaving a partial message in the skb.
The caller then propagates -ENOBUFS directly, which the netlink dump
infrastructure treats as a fatal error, aborting the entire dump.
The correct netlink dump convention is:
- Cancel any partial message with genlmsg_cancel()
- If prior messages were added to the skb (skb->len > 0), save the
current iterator position and return skb->len to paginate
- Only return a negative errno when no messages fit at all
Fix compose_msg to cancel the partial message on all nla_put failure
paths, and fix the caller to paginate when possible rather than
returning a fatal error.
A second defect surfaces once pagination actually works: cb->args[1]
records the resume index within the pool named by cb->args[0], but the
inner loop applied it to every pool from cb->args[0] onward. After a
mid-pool pause, a later dump call drains the resume pool and continues
into subsequent pools within the same call, where the stale cb->args[1]
caused the first N threads of each following pool to be skipped. On
per-CPU or per-node pool configurations this silently dropped active
requests from the dump. Apply the saved thread index only to the pool
matching cb->args[0], and start every subsequent pool from thread 0.
Fixes: bd9d6a3efa ("NFSD: add rpc_status netlink support")
Cc: stable@vger.kernel.org
Assisted-by: Claude:claude-opus-4-8
Signed-off-by: Jeff Layton <jlayton@kernel.org>
[ cel: fold in 20/21 to avoid bisect hazard ]
Link: https://patch.msgid.link/20260611-nfsd-testing-v2-3-5b90e276f2d9@kernel.org
Signed-off-by: Chuck Lever <cel@kernel.org>
This commit is contained in:
@@ -1452,7 +1452,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
|
||||
nla_put_s64(skb, NFSD_A_RPC_STATUS_SERVICE_TIME,
|
||||
ktime_to_us(genl_rqstp->rq_stime),
|
||||
NFSD_A_RPC_STATUS_PAD))
|
||||
return -ENOBUFS;
|
||||
goto out_cancel;
|
||||
|
||||
switch (genl_rqstp->rq_saddr.ss_family) {
|
||||
case AF_INET: {
|
||||
@@ -1468,7 +1468,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
|
||||
s_in->sin_port) ||
|
||||
nla_put_be16(skb, NFSD_A_RPC_STATUS_DPORT,
|
||||
d_in->sin_port))
|
||||
return -ENOBUFS;
|
||||
goto out_cancel;
|
||||
break;
|
||||
}
|
||||
case AF_INET6: {
|
||||
@@ -1484,7 +1484,7 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
|
||||
s_in->sin6_port) ||
|
||||
nla_put_be16(skb, NFSD_A_RPC_STATUS_DPORT,
|
||||
d_in->sin6_port))
|
||||
return -ENOBUFS;
|
||||
goto out_cancel;
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -1492,10 +1492,14 @@ static int nfsd_genl_rpc_status_compose_msg(struct sk_buff *skb,
|
||||
for (i = 0; i < genl_rqstp->rq_opcnt; i++)
|
||||
if (nla_put_u32(skb, NFSD_A_RPC_STATUS_COMPOUND_OPS,
|
||||
genl_rqstp->rq_opnum[i]))
|
||||
return -ENOBUFS;
|
||||
goto out_cancel;
|
||||
|
||||
genlmsg_end(skb, hdr);
|
||||
return 0;
|
||||
|
||||
out_cancel:
|
||||
genlmsg_cancel(skb, hdr);
|
||||
return -ENOBUFS;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1523,10 +1527,20 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
|
||||
|
||||
for (i = 0; i < nn->nfsd_serv->sv_nrpools; i++) {
|
||||
struct svc_rqst *rqstp;
|
||||
long thread_skip = 0;
|
||||
|
||||
if (i < cb->args[0]) /* already consumed */
|
||||
continue;
|
||||
|
||||
/*
|
||||
* The saved thread index only applies to the pool the dump
|
||||
* was resumed in. Subsequent pools must start from thread 0,
|
||||
* otherwise their first cb->args[1] threads are silently
|
||||
* skipped.
|
||||
*/
|
||||
if (i == cb->args[0])
|
||||
thread_skip = cb->args[1];
|
||||
|
||||
rqstp_index = 0;
|
||||
list_for_each_entry_rcu(rqstp,
|
||||
&nn->nfsd_serv->sv_pools[i].sp_all_threads,
|
||||
@@ -1534,7 +1548,7 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
|
||||
struct nfsd_genl_rqstp genl_rqstp = {};
|
||||
unsigned int status_counter;
|
||||
|
||||
if (rqstp_index++ < cb->args[1]) /* already consumed */
|
||||
if (rqstp_index++ < thread_skip) /* already consumed */
|
||||
continue;
|
||||
/*
|
||||
* Acquire rq_status_counter before parsing the rqst
|
||||
@@ -1588,8 +1602,14 @@ int nfsd_nl_rpc_status_get_dumpit(struct sk_buff *skb,
|
||||
|
||||
ret = nfsd_genl_rpc_status_compose_msg(skb, cb,
|
||||
&genl_rqstp);
|
||||
if (ret)
|
||||
if (ret) {
|
||||
if (skb->len) {
|
||||
cb->args[0] = i;
|
||||
cb->args[1] = rqstp_index - 1;
|
||||
ret = skb->len;
|
||||
}
|
||||
goto out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user