* [PATCH net v10 1/3] rxrpc: account encapsulated packets to UDP socket
2026-10-16 19:59 [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion Zihan Xi
@ 2026-10-16 19:59 ` Zihan Xi
2026-10-16 19:59 ` [PATCH net v10 2/3] rxrpc: bound connection and OOB receive queues Zihan Xi
` (3 subsequent siblings)
4 siblings, 0 replies; 9+ messages in thread
From: Zihan Xi @ 2026-10-16 19:59 UTC (permalink / raw)
To: linux-afs, netdev
Cc: dhowells, marc.dionne, davem, edumazet, pabeni, horms, vega, root,
zihanx
rxrpc_encap_rcv() queues encapsulated UDP packets on the RxRPC local
queue without charging them to the UDP socket. If the I/O thread cannot
keep up, the queue can grow without bound.
Charge each packet against the tunnel socket under
sk_receive_queue.lock. Derive the receive-buffer limit from the
advertised window, retaining headroom for control and error traffic.
Drop packets when the buffer or protocol memory budget is exhausted,
while preserving UDP's empty-queue exception.
Orphan charged skbs when the I/O thread dequeues them. Since
skb_set_owner_r() does not hold a socket reference, clear sk_user_data
under RCU and release the socket only after purging the local queues.
The remaining patches bound RxRPC queues that can retain skbs after the
UDP receive-memory charge is released.
Fixes: 446b3e14525b ("rxrpc: Move packet reception processing into I/O thread")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Co-developed-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
---
changes in v10:
- No code changes; carry this patch forward unchanged in the updated
three-patch series.
- v9 Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
changes in v9:
- Split the fix into UDP accounting, connection/OOB queue budgeting,
and DATA receive-queue budgeting patches.
- Correct the Fixes attribution for the asynchronous receive queues.
- v8 Link: https://lore.kernel.org/all/cover.1791428529.git.zihanx@nebusec.ai/
changes in v8:
- Rebase onto net/main at 6d25ffca055a and resend after the previous
posting became stale.
- v7 Link: https://lore.kernel.org/all/cover.1790427022.git.zihanx@nebusec.ai/
changes in v7:
- Rebase the fix onto net/main at a7bfaba4823e.
- v6 Link: https://lore.kernel.org/all/cover.1789608557.git.zihanx@nebusec.ai/
changes in v6:
- Refresh the tunnel socket receive-buffer cap when rxrpc_rx_window_size
increases, without shrinking an existing socket.
- Distinguish socket receive-buffer exhaustion from protocol memory
accounting failures in the drop reason.
- Cap the window-derived value at sysctl_rmem_max and only grow an
existing sk_rcvbuf.
- v5 Link: https://lore.kernel.org/all/cover.1789273347.git.zihanx@nebusec.ai/
changes in v5:
- Size the tunnel sk_rcvbuf from one advertised DATA window, with
additional headroom for ACKs and ICMP, capped by sysctl_rmem_max.
- Drop the dst instead of skb_dst_force(); keep skb->dev = NULL.
- Document the runtime receive-window sizing limitation.
- v4 Link: https://lore.kernel.org/all/cover.1788878590.git.zihanx@nebusec.ai/
changes in v4:
- Serialize UDP rmem charge and uncharge with sk_receive_queue.lock.
- Orphan only PACKET skbs charged by encap_rcv(); preserve error skb
ownership.
- Clarify the callback-listener setup and distinguish the unprivileged
flood from extra conditions used for the recorded OOM.
- v3 Link: https://lore.kernel.org/all/cover.1788539302.git.zihanx@nebusec.ai/
changes in v3:
- Orphan skbs when the I/O thread dequeues them so UDP rmem ownership
does not follow packets onto call or connection queues.
- Clarify the affected source files and the recorded OOM context.
- v2 Link: https://lore.kernel.org/all/cover.1785339953.git.zihanx@nebusec.ai/
changes in v2:
- Use sk_drops_inc(), correct the Fixes attribution, and refresh the
baseline reproducer and decoded crash log.
- Explain why packetdrill does not express the sustained unique-call
flood used by this reproducer.
- v1 Link: https://lore.kernel.org/all/cover.1784742007.git.zihanx@nebusec.ai/
---
net/rxrpc/ar-internal.h | 1 +
net/rxrpc/io_thread.c | 61 ++++++++++++++++++++++++++++++++++++++--
net/rxrpc/local_object.c | 24 ++++++++++++++--
3 files changed, 82 insertions(+), 4 deletions(-)
diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h
index 865f05fe37ab9..b079ec98aaa74 100644
--- a/net/rxrpc/ar-internal.h
+++ b/net/rxrpc/ar-internal.h
@@ -1323,6 +1323,7 @@ void rxrpc_send_version_request(struct rxrpc_local *local,
* local_object.c
*/
void rxrpc_local_dont_fragment(const struct rxrpc_local *local, bool set);
+void rxrpc_adjust_rcvbuf(struct sock *sk);
struct rxrpc_local *rxrpc_lookup_local(struct net *, const struct sockaddr_rxrpc *);
struct rxrpc_local *rxrpc_get_local(struct rxrpc_local *, enum rxrpc_local_trace);
struct rxrpc_local *rxrpc_get_local_maybe(struct rxrpc_local *, enum rxrpc_local_trace);
diff --git a/net/rxrpc/io_thread.c b/net/rxrpc/io_thread.c
index dc5184a2fa9d1..634a6e1cab87a 100644
--- a/net/rxrpc/io_thread.c
+++ b/net/rxrpc/io_thread.c
@@ -7,12 +7,37 @@
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
+#include <net/udp.h>
+
#include "ar-internal.h"
static int rxrpc_input_packet_on_conn(struct rxrpc_connection *conn,
struct sockaddr_rxrpc *peer_srx,
struct sk_buff *skb);
+/*
+ * Drop UDP rmem ownership for packets charged in encap_rcv().
+ * sk_forward_alloc is serialised by sk_receive_queue.lock.
+ */
+static void rxrpc_skb_orphan_udp(struct sk_buff *skb)
+{
+ struct sock *sk = skb->sk;
+
+ if (!sk)
+ return;
+
+ spin_lock_bh(&sk->sk_receive_queue.lock);
+ skb_orphan(skb);
+ spin_unlock_bh(&sk->sk_receive_queue.lock);
+}
+
+static void rxrpc_encap_rcv_drop(struct sock *udp_sk, struct sk_buff *skb,
+ enum skb_drop_reason reason)
+{
+ sk_drops_inc(udp_sk);
+ sk_skb_reason_drop(udp_sk, skb, reason);
+}
+
/*
* handle data received on the local endpoint
* - may be called in interrupt context
@@ -28,6 +53,10 @@ int rxrpc_encap_rcv(struct sock *udp_sk, struct sk_buff *skb)
struct sk_buff_head *rx_queue;
struct rxrpc_local *local = rcu_dereference_sk_user_data(udp_sk);
struct task_struct *io_thread;
+ enum skb_drop_reason reason;
+ unsigned int headroom;
+ unsigned int rcvbuf;
+ unsigned int rmem;
if (unlikely(!local)) {
kfree_skb(skb);
@@ -41,8 +70,6 @@ int rxrpc_encap_rcv(struct sock *udp_sk, struct sk_buff *skb)
if (skb->tstamp == 0)
skb->tstamp = ktime_get_real();
- skb->mark = RXRPC_SKB_MARK_PACKET;
- rxrpc_new_skb(skb, rxrpc_skb_new_encap_rcv);
rx_queue = &local->rx_queue;
#ifdef CONFIG_AF_RXRPC_INJECT_RX_DELAY
if (rxrpc_inject_rx_delay ||
@@ -52,9 +79,36 @@ int rxrpc_encap_rcv(struct sock *udp_sk, struct sk_buff *skb)
}
#endif
+ headroom = SKB_TRUESIZE(RXRPC_JUMBO(1)) * 2;
+ spin_lock(&udp_sk->sk_receive_queue.lock);
+ rxrpc_adjust_rcvbuf(udp_sk);
+ rcvbuf = READ_ONCE(udp_sk->sk_rcvbuf);
+ rmem = atomic_read(&udp_sk->sk_rmem_alloc);
+ /* Permit one skb when there is no receive memory charged, as UDP does. */
+ if (rmem && rmem + skb->truesize + headroom >= rcvbuf) {
+ reason = SKB_DROP_REASON_SOCKET_RCVBUFF;
+ goto drop;
+ }
+ if (!sk_rmem_schedule(udp_sk, skb, skb->truesize)) {
+ reason = SKB_DROP_REASON_PROTO_MEM;
+ goto drop;
+ }
+
+ skb->dev = NULL;
+ skb_set_owner_r(skb, udp_sk);
+ spin_unlock(&udp_sk->sk_receive_queue.lock);
+ skb_dst_drop(skb);
+
+ skb->mark = RXRPC_SKB_MARK_PACKET;
+ rxrpc_new_skb(skb, rxrpc_skb_new_encap_rcv);
skb_queue_tail(rx_queue, skb);
wake_up_process(io_thread);
return 0;
+
+drop:
+ spin_unlock(&udp_sk->sk_receive_queue.lock);
+ rxrpc_encap_rcv_drop(udp_sk, skb, reason);
+ return 0;
}
/*
@@ -471,6 +525,9 @@ int rxrpc_io_thread(void *data)
/* Distribute packets and errors. */
while ((skb = __skb_dequeue(&rx_queue))) {
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+
+ if (skb->mark == RXRPC_SKB_MARK_PACKET)
+ rxrpc_skb_orphan_udp(skb);
switch (skb->mark) {
case RXRPC_SKB_MARK_PACKET:
skb->priority = 0;
diff --git a/net/rxrpc/local_object.c b/net/rxrpc/local_object.c
index 169f9dfdaa77f..f519a93059244 100644
--- a/net/rxrpc/local_object.c
+++ b/net/rxrpc/local_object.c
@@ -22,6 +22,19 @@
static void rxrpc_local_rcu(struct rcu_head *);
+void rxrpc_adjust_rcvbuf(struct sock *sk)
+{
+ u32 rcvbuf, old_rcvbuf;
+
+ rcvbuf = READ_ONCE(rxrpc_rx_window_size) *
+ SKB_TRUESIZE(RXRPC_JUMBO(1)) * 2;
+ rcvbuf += rcvbuf / 4;
+ rcvbuf = min_t(u32, rcvbuf, READ_ONCE(sysctl_rmem_max));
+ old_rcvbuf = READ_ONCE(sk->sk_rcvbuf);
+ if (rcvbuf > old_rcvbuf)
+ WRITE_ONCE(sk->sk_rcvbuf, rcvbuf);
+}
+
/*
* Handle an ICMP/ICMP6 error turning up at the tunnel. Push it through the
* usual mechanism so that it gets parsed and presented through the UDP
@@ -198,6 +211,9 @@ static int rxrpc_open_socket(struct rxrpc_local *local, struct net *net)
/* set the socket up */
usk = local->socket->sk;
+ spin_lock_bh(&usk->sk_receive_queue.lock);
+ rxrpc_adjust_rcvbuf(usk);
+ spin_unlock_bh(&usk->sk_receive_queue.lock);
usk->sk_error_report = rxrpc_error_report;
switch (srx->transport.family) {
@@ -437,8 +453,8 @@ void rxrpc_destroy_local(struct rxrpc_local *local)
if (socket) {
local->socket = NULL;
kernel_sock_shutdown(socket, SHUT_RDWR);
- socket->sk->sk_user_data = NULL;
- sock_release(socket);
+ rcu_assign_sk_user_data(socket->sk, NULL);
+ synchronize_rcu();
}
/* At this point, there should be no more packets coming in to the
@@ -448,6 +464,10 @@ void rxrpc_destroy_local(struct rxrpc_local *local)
rxrpc_purge_queue(&local->rx_delay_queue);
#endif
rxrpc_purge_queue(&local->rx_queue);
+
+ if (socket)
+ sock_release(socket);
+
rxrpc_purge_client_connections(local);
page_frag_cache_drain(&local->tx_alloc);
}
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* [PATCH net v10 2/3] rxrpc: bound connection and OOB receive queues
2026-10-16 19:59 [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion Zihan Xi
2026-10-16 19:59 ` [PATCH net v10 1/3] rxrpc: account encapsulated packets to UDP socket Zihan Xi
@ 2026-10-16 19:59 ` Zihan Xi
2026-10-20 20:21 ` netdev-bot+sashiko
2026-10-16 19:59 ` [PATCH net v10 3/3] rxrpc: bound DATA " Zihan Xi
` (2 subsequent siblings)
4 siblings, 1 reply; 9+ messages in thread
From: Zihan Xi @ 2026-10-16 19:59 UTC (permalink / raw)
To: linux-afs, netdev
Cc: dhowells, marc.dionne, davem, edumazet, pabeni, horms, vega, root,
zihanx
After rxrpc_io_thread() dequeues a packet, the UDP receive-memory charge
is released. Connection-level packets can remain queued on conn->rx_queue,
while managed RXGK challenges can remain on the socket's OOB queue and
pending-response set.
Account both paths against a per-local receive budget. Release the charge
when a packet is processed, answered, freed or purged, and retain it when a
secured service packet is returned to the local I/O queue.
Fixes: 17926a79320a ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both")
Fixes: 5800b1cf3fd8 ("rxrpc: Allow CHALLENGEs to the passed to the app for a RESPONSE")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Co-developed-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
---
changes in v10:
- Release the retained connection reference when an already-aborted
connection causes queued CHALLENGE processing to return early.
- Give queued OOB challenge skbs a dedicated internal skb mark, separate
from the public OOB type enum.
- v9 Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
changes in v9:
- Split connection/OOB queue budgeting from DATA receive-queue
budgeting.
- Add the Fixes attribution for the RXGK challenge OOB path.
- Order receive-budget helper declarations per netdev reverse-xmas style.
- v8 Link: https://lore.kernel.org/all/cover.1791428529.git.zihanx@nebusec.ai/
---
net/rxrpc/ar-internal.h | 3 +++
net/rxrpc/conn_event.c | 29 +++++++++++++++++++++++----
net/rxrpc/conn_object.c | 19 +++++++++++++++---
net/rxrpc/io_thread.c | 2 ++
net/rxrpc/local_object.c | 1 +
net/rxrpc/oob.c | 20 +++++++++++--------
net/rxrpc/recvmsg.c | 4 ++--
net/rxrpc/skbuff.c | 43 +++++++++++++++++++++++++++++++++++++++-
8 files changed, 103 insertions(+), 18 deletions(-)
diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h
index b079ec98aaa74..36fc4609181e1 100644
--- a/net/rxrpc/ar-internal.h
+++ b/net/rxrpc/ar-internal.h
@@ -360,6 +360,7 @@ struct rxrpc_local {
struct sk_buff_head rx_delay_queue; /* Delay injection queue */
#endif
struct sk_buff_head rx_queue; /* Received packets */
+ atomic_long_t rx_async_queue_bytes; /* Outstanding async Rx bytes */
struct list_head conn_attend_q; /* Conns requiring immediate attention */
struct list_head call_attend_q; /* Calls requiring immediate attention */
@@ -1516,6 +1517,8 @@ void rxrpc_new_skb(struct sk_buff *, enum rxrpc_skb_trace);
void rxrpc_see_skb(struct sk_buff *, enum rxrpc_skb_trace);
void rxrpc_get_skb(struct sk_buff *, enum rxrpc_skb_trace);
void rxrpc_free_skb(struct sk_buff *, enum rxrpc_skb_trace);
+bool rxrpc_charge_async_queue(struct rxrpc_local *, struct sk_buff *);
+void rxrpc_uncharge_async_queue(struct rxrpc_local *, struct sk_buff *);
void rxrpc_purge_queue(struct sk_buff_head *);
/*
diff --git a/net/rxrpc/conn_event.c b/net/rxrpc/conn_event.c
index 611c790bc6d0c..d37cef2523997 100644
--- a/net/rxrpc/conn_event.c
+++ b/net/rxrpc/conn_event.c
@@ -391,8 +391,17 @@ static void rxrpc_do_process_connection(struct rxrpc_connection *conn)
/* go through the conn-level event packets, releasing the ref on this
* connection that each one has when we've finished with it */
while ((skb = skb_dequeue(&conn->rx_queue))) {
+ struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
rxrpc_see_skb(skb, rxrpc_skb_see_conn_work);
rxrpc_process_event(conn, skb);
+ if (skb->mark != RXRPC_SKB_MARK_SERVICE_CONN_SECURED)
+ rxrpc_uncharge_async_queue(conn->local, skb);
+ if (sp->hdr.type == RXRPC_PACKET_TYPE_CHALLENGE && sp->chall.conn) {
+ rxrpc_put_connection(
+ sp->chall.conn,
+ rxrpc_conn_put_challenge_input);
+ sp->chall.conn = NULL;
+ }
rxrpc_free_skb(skb, rxrpc_skb_put_conn_work);
}
}
@@ -414,15 +423,22 @@ void rxrpc_process_connection(struct work_struct *work)
* post connection-level events to the connection
* - this includes challenges, responses, some aborts and call terminal packet
* retransmission.
+ * - bound the queued memory independently of the UDP rmem charge.
*/
-static void rxrpc_post_packet_to_conn(struct rxrpc_connection *conn,
+static bool rxrpc_post_packet_to_conn(struct rxrpc_connection *conn,
struct sk_buff *skb)
{
+ struct rxrpc_local *local = conn->local;
+
+ if (!rxrpc_charge_async_queue(local, skb))
+ return false;
+
_enter("%p,%p", conn, skb);
rxrpc_get_skb(skb, rxrpc_skb_get_conn_work);
skb_queue_tail(&conn->rx_queue, skb);
rxrpc_queue_conn(conn, rxrpc_conn_queue_rx_work);
+ return true;
}
/*
@@ -442,7 +458,10 @@ static bool rxrpc_post_challenge(struct rxrpc_connection *conn,
rxrpc_get_connection(conn, rxrpc_conn_get_challenge_input);
if (!conn->security->challenge_to_recvmsg) {
- rxrpc_post_packet_to_conn(conn, skb);
+ if (!rxrpc_post_packet_to_conn(conn, skb)) {
+ rxrpc_put_connection(conn, rxrpc_conn_put_challenge_input);
+ sp->chall.conn = NULL;
+ }
return true;
}
@@ -480,8 +499,10 @@ static bool rxrpc_post_challenge(struct rxrpc_connection *conn,
return false;
}
- if (!call)
- rxrpc_post_packet_to_conn(conn, skb);
+ if (!call && !rxrpc_post_packet_to_conn(conn, skb)) {
+ rxrpc_put_connection(conn, rxrpc_conn_put_challenge_input);
+ sp->chall.conn = NULL;
+ }
return true;
}
diff --git a/net/rxrpc/conn_object.c b/net/rxrpc/conn_object.c
index 0ece717db0f85..d85345e014a9f 100644
--- a/net/rxrpc/conn_object.c
+++ b/net/rxrpc/conn_object.c
@@ -300,6 +300,19 @@ static void rxrpc_rcu_free_connection(struct rcu_head *rcu)
wake_up_var(&rxnet->nr_conns);
}
+/*
+ * Purge a connection's receive queue, updating the local queue accounting.
+ */
+static void rxrpc_purge_conn_queue(struct rxrpc_connection *conn)
+{
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(&conn->rx_queue))) {
+ rxrpc_uncharge_async_queue(conn->local, skb);
+ rxrpc_free_skb(skb, rxrpc_skb_put_purge);
+ }
+}
+
/*
* Clean up a dead connection.
*/
@@ -329,7 +342,7 @@ static void rxrpc_clean_up_connection(struct work_struct *work)
conn->peer->pmtud_pending = true;
}
- rxrpc_purge_queue(&conn->rx_queue);
+ rxrpc_purge_conn_queue(conn);
rxrpc_free_skb(conn->tx_response, rxrpc_skb_put_response);
rxrpc_kill_client_conn(conn);
@@ -338,15 +351,15 @@ static void rxrpc_clean_up_connection(struct work_struct *work)
key_put(conn->key);
rxrpc_put_bundle(conn->bundle, rxrpc_bundle_put_conn);
rxrpc_put_peer(conn->peer, rxrpc_peer_put_conn);
- rxrpc_put_local(conn->local, rxrpc_local_put_kill_conn);
/* Drain the Rx queue. Note that even though we've unpublished, an
* incoming packet could still be being added to our Rx queue, so we
* will need to drain it again in the RCU cleanup handler.
*/
- rxrpc_purge_queue(&conn->rx_queue);
+ rxrpc_purge_conn_queue(conn);
page_frag_cache_drain(&conn->tx_data_alloc);
+ rxrpc_put_local(conn->local, rxrpc_local_put_kill_conn);
call_rcu(&conn->rcu, rxrpc_rcu_free_connection);
}
diff --git a/net/rxrpc/io_thread.c b/net/rxrpc/io_thread.c
index 634a6e1cab87a..8679402c03e1b 100644
--- a/net/rxrpc/io_thread.c
+++ b/net/rxrpc/io_thread.c
@@ -528,6 +528,8 @@ int rxrpc_io_thread(void *data)
if (skb->mark == RXRPC_SKB_MARK_PACKET)
rxrpc_skb_orphan_udp(skb);
+ else if (skb->mark == RXRPC_SKB_MARK_SERVICE_CONN_SECURED)
+ rxrpc_uncharge_async_queue(sp->poke_conn->local, skb);
switch (skb->mark) {
case RXRPC_SKB_MARK_PACKET:
skb->priority = 0;
diff --git a/net/rxrpc/local_object.c b/net/rxrpc/local_object.c
index f519a93059244..8e43f77d43683 100644
--- a/net/rxrpc/local_object.c
+++ b/net/rxrpc/local_object.c
@@ -137,6 +137,7 @@ static struct rxrpc_local *rxrpc_alloc_local(struct net *net,
skb_queue_head_init(&local->rx_delay_queue);
#endif
skb_queue_head_init(&local->rx_queue);
+ atomic_long_set(&local->rx_async_queue_bytes, 0);
INIT_LIST_HEAD(&local->conn_attend_q);
INIT_LIST_HEAD(&local->call_attend_q);
diff --git a/net/rxrpc/oob.c b/net/rxrpc/oob.c
index c80ee2487d094..e3d135b12d22d 100644
--- a/net/rxrpc/oob.c
+++ b/net/rxrpc/oob.c
@@ -46,7 +46,9 @@ bool rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb)
sk = &rx->sk;
spin_lock_irq(&rx->recvmsg_lock);
- if (sk->sk_state < RXRPC_CLOSE) {
+ if (sk->sk_state < RXRPC_CLOSE &&
+ rxrpc_charge_async_queue(sp->chall.conn->local, skb)) {
+ skb->mark = RXRPC_SKB_MARK_CHALLENGE;
skb->skb_mstamp_ns = rx->oob_id_counter++;
rxrpc_get_skb(skb, rxrpc_skb_get_post_oob);
skb_queue_tail(&rx->recvmsg_oobq, skb);
@@ -194,7 +196,7 @@ static int rxrpc_respond_to_oob(struct rxrpc_sock *rx,
switch (p->command) {
case RXRPC_OOB_CMD_RESPOND:
ret = -EPROTO;
- if (skb->mark != RXRPC_OOB_CHALLENGE)
+ if (skb->mark != RXRPC_SKB_MARK_CHALLENGE)
break;
conn = sp->chall.conn;
ret = -EOPNOTSUPP;
@@ -214,7 +216,8 @@ static int rxrpc_respond_to_oob(struct rxrpc_sock *rx,
}
switch (skb->mark) {
- case RXRPC_OOB_CHALLENGE:
+ case RXRPC_SKB_MARK_CHALLENGE:
+ rxrpc_uncharge_async_queue(sp->chall.conn->local, skb);
rxrpc_put_connection(sp->chall.conn, rxrpc_conn_put_oob);
break;
}
@@ -275,10 +278,10 @@ enum rxrpc_oob_type rxrpc_kernel_query_oob(struct sk_buff *oob,
unsigned long *_peer_appdata)
{
struct rxrpc_skb_priv *sp = rxrpc_skb(oob);
- enum rxrpc_oob_type type = oob->mark;
+ enum rxrpc_oob_type type = RXRPC_OOB_CHALLENGE;
- switch (type) {
- case RXRPC_OOB_CHALLENGE:
+ switch (oob->mark) {
+ case RXRPC_SKB_MARK_CHALLENGE:
*_peer = sp->chall.conn->peer;
*_peer_appdata = sp->chall.conn->peer->app_data;
break;
@@ -312,7 +315,7 @@ struct sk_buff *rxrpc_kernel_dequeue_oob(struct socket *sock,
oob = skb_dequeue(&rx->recvmsg_oobq);
if (oob)
- *_type = oob->mark;
+ *_type = RXRPC_OOB_CHALLENGE;
return oob;
}
EXPORT_SYMBOL(rxrpc_kernel_dequeue_oob);
@@ -328,7 +331,8 @@ void rxrpc_kernel_free_oob(struct sk_buff *oob)
struct rxrpc_skb_priv *sp = rxrpc_skb(oob);
switch (oob->mark) {
- case RXRPC_OOB_CHALLENGE:
+ case RXRPC_SKB_MARK_CHALLENGE:
+ rxrpc_uncharge_async_queue(sp->chall.conn->local, oob);
rxrpc_put_connection(sp->chall.conn, rxrpc_conn_put_oob);
break;
}
diff --git a/net/rxrpc/recvmsg.c b/net/rxrpc/recvmsg.c
index efcba4b2e74f0..172c72944cddd 100644
--- a/net/rxrpc/recvmsg.c
+++ b/net/rxrpc/recvmsg.c
@@ -248,8 +248,8 @@ static int rxrpc_recvmsg_oob(struct socket *sock, struct msghdr *msg,
if (ret < 0)
return ret;
- switch ((enum rxrpc_oob_type)skb->mark) {
- case RXRPC_OOB_CHALLENGE:
+ switch (skb->mark) {
+ case RXRPC_SKB_MARK_CHALLENGE:
need_response = true;
ret = rxrpc_recvmsg_challenge(sock, msg, skb, flags);
break;
diff --git a/net/rxrpc/skbuff.c b/net/rxrpc/skbuff.c
index e2169d1a14b5f..a5113a284456e 100644
--- a/net/rxrpc/skbuff.c
+++ b/net/rxrpc/skbuff.c
@@ -58,6 +58,39 @@ void rxrpc_free_skb(struct sk_buff *skb, enum rxrpc_skb_trace why)
}
}
+/*
+ * Charge an skb retained outside UDP receive-memory accounting.
+ */
+bool rxrpc_charge_async_queue(struct rxrpc_local *local, struct sk_buff *skb)
+{
+ struct sock *sk = local->socket->sk;
+ long rcvbuf;
+ long queued;
+
+ rcvbuf = READ_ONCE(sk->sk_rcvbuf);
+
+ for (;;) {
+ queued = atomic_long_read(&local->rx_async_queue_bytes);
+ if (queued &&
+ (queued >= rcvbuf ||
+ (long)skb->truesize >= rcvbuf - queued)) {
+ sk_drops_inc(sk);
+ return false;
+ }
+ if (atomic_long_cmpxchg(&local->rx_async_queue_bytes, queued,
+ queued + (long)skb->truesize) == queued)
+ return true;
+ }
+}
+
+/*
+ * Release the asynchronous receive charge on an skb.
+ */
+void rxrpc_uncharge_async_queue(struct rxrpc_local *local, struct sk_buff *skb)
+{
+ atomic_long_sub(skb->truesize, &local->rx_async_queue_bytes);
+}
+
/*
* Clear a queue of socket buffers.
*/
@@ -66,7 +99,15 @@ void rxrpc_purge_queue(struct sk_buff_head *list)
struct sk_buff *skb;
while ((skb = skb_dequeue((list))) != NULL) {
- int n = atomic_dec_return(select_skb_count(skb));
+ int n;
+
+ if (skb->mark == RXRPC_SKB_MARK_SERVICE_CONN_SECURED) {
+ struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+
+ rxrpc_uncharge_async_queue(sp->poke_conn->local, skb);
+ rxrpc_put_connection(sp->poke_conn, rxrpc_conn_put_poke);
+ }
+ n = atomic_dec_return(select_skb_count(skb));
trace_rxrpc_skb(skb, refcount_read(&skb->users), n,
rxrpc_skb_put_purge);
consume_skb(skb);
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH net v10 2/3] rxrpc: bound connection and OOB receive queues
2026-10-16 19:59 ` [PATCH net v10 2/3] rxrpc: bound connection and OOB receive queues Zihan Xi
@ 2026-10-20 20:21 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 20:21 UTC (permalink / raw)
To: zihanx
Cc: linux-afs, netdev, dhowells, marc.dionne, davem, edumazet, pabeni,
horms, vega, root, kuba
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 1 (1) · Medium: 0 · Low: 2 (1)
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] The new async receive budget lives on the rxrpc_local, not on
the socket.
- [Low] The patch includes fixes and semantic changes that its commit
message does not describe: (a) rxrpc_purge_queue() now drops…
- [Low] rxrpc_notify_socket_oob() (net/rxrpc/oob.c) now sets skb->mark =
RXRPC_SKB_MARK_CHALLENGE on the skb the I/O thread is still processing.
Pre-existing issues:
- [High] This problem wasn't introduced by this patch, but the patch now
adds the new receive charge to it.
- [Low] This problem wasn't introduced by this patch, but the comment
above the second rxrpc_purge_conn_queue() in…
--
rxrpc: bound connection and OOB receive queues
This patch charges packets held on conn->rx_queue, and managed RXGK
challenges held on the socket OOB queues, to a new per-local budget
(rx_async_queue_bytes) that is capped by the UDP socket's sk_rcvbuf. The
charge is released when a packet is processed, answered, freed or purged.
Queued OOB challenges also get their own skb mark,
RXRPC_SKB_MARK_CHALLENGE.
Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
> Account both paths against a per-local receive budget. Release the charge
> when a packet is processed, answered, freed or purged, and retain it when a
> secured service packet is returned to the local I/O queue.
[Severity: Low]
This isn't a bug, but the patch makes a few changes that the commit message
doesn't mention.
rxrpc_purge_queue() now drops sp->poke_conn for
RXRPC_SKB_MARK_SERVICE_CONN_SECURED skbs that are still on local->rx_queue
at endpoint teardown. Before, it only called consume_skb(), so that
connection reference leaked.
rxrpc_do_process_connection() now puts sp->chall.conn when
rxrpc_process_event() returns -ECONNABORTED before it reaches the CHALLENGE
case. That reference also leaked before.
OOB challenge skbs now get an explicit skb->mark = RXRPC_SKB_MARK_CHALLENGE.
The old code relied on RXRPC_SKB_MARK_PACKET == RXRPC_OOB_CHALLENGE == 0.
Also, rxrpc_kernel_query_oob() and rxrpc_kernel_dequeue_oob() now hard-code
RXRPC_OOB_CHALLENGE instead of returning skb->mark.
Of these, only the second shows up, in the v10 changelog below the cut, and
that text is dropped when the patch is applied.
Since this is Cc: stable, could the ref-leak fixes be split out with their
own Fixes tags, or at least described in the commit message?
> diff --git a/net/rxrpc/conn_event.c b/net/rxrpc/conn_event.c
> index 611c790bc6d0c..d37cef2523997 100644
> --- a/net/rxrpc/conn_event.c
> +++ b/net/rxrpc/conn_event.c
[ ... ]
> @@ -414,15 +423,22 @@ void rxrpc_process_connection(struct work_struct *work)
> * post connection-level events to the connection
> * - this includes challenges, responses, some aborts and call terminal packet
> * retransmission.
> + * - bound the queued memory independently of the UDP rmem charge.
> */
> -static void rxrpc_post_packet_to_conn(struct rxrpc_connection *conn,
> +static bool rxrpc_post_packet_to_conn(struct rxrpc_connection *conn,
> struct sk_buff *skb)
> {
> + struct rxrpc_local *local = conn->local;
> +
> + if (!rxrpc_charge_async_queue(local, skb))
> + return false;
> +
> _enter("%p,%p", conn, skb);
>
> rxrpc_get_skb(skb, rxrpc_skb_get_conn_work);
> skb_queue_tail(&conn->rx_queue, skb);
> rxrpc_queue_conn(conn, rxrpc_conn_queue_rx_work);
> + return true;
> }
[Severity: High]
This is a pre-existing issue, but the new charge now gets caught up in it.
What happens if a packet is queued here while the last socket on the local
is being released?
rxrpc_release_sock() calls flush_workqueue(rxrpc_workqueue) before
rxrpc_unuse_local(). When active_users reaches 0, rxrpc_unuse_local() calls
kthread_stop(local->io_thread). The I/O thread keeps draining
local->rx_queue and only then calls rxrpc_destroy_local().
In that window, an rxkad CHALLENGE for a live client connection (for
example an idle one on local->idle_client_conns) can take this path:
rxrpc_post_challenge()
sp->chall.conn = rxrpc_get_connection(conn, ...)
rxrpc_post_packet_to_conn()
rxrpc_charge_async_queue()
skb_queue_tail(&conn->rx_queue, skb)
rxrpc_queue_conn()
The worker then runs:
rxrpc_process_connection() {
if (__rxrpc_use_local(conn->local, rxrpc_local_use_conn_work)) {
rxrpc_do_process_connection(conn);
...
}
}
__rxrpc_use_local() fails because active_users is 0, so the worker never
touches conn->rx_queue.
The skb now holds a connection reference while it sits on that same
connection's rx_queue. When rxrpc_clean_up_local_conns() drops the idle-list
reference, the refcount stays at 1. That means rxrpc_clean_up_connection()
and rxrpc_purge_conn_queue() never run.
Does this leak the skb, the connection, and its local and peer references?
The conn would also stay on rxnet->conn_proc_list. Netns teardown could then
hit ASSERT(list_empty(&rxnet->conn_proc_list)) in
rxrpc_destroy_all_connections(), which is BUG() in rxrpc, or hang in
wait_var_event() on nr_conns.
The stranded rx_async_queue_bytes charge looks harmless, because that local
is dead and is never reused.
When the local can't be used, could the skipped-worker path purge and
uncharge conn->rx_queue (dropping chall.conn)? Alternatively, could teardown
stop the producer before the final workqueue flush?
[ ... ]
> diff --git a/net/rxrpc/conn_object.c b/net/rxrpc/conn_object.c
> index 0ece717db0f85..d85345e014a9f 100644
> --- a/net/rxrpc/conn_object.c
> +++ b/net/rxrpc/conn_object.c
[ ... ]
> @@ -338,15 +351,15 @@ static void rxrpc_clean_up_connection(struct work_struct *work)
> key_put(conn->key);
> rxrpc_put_bundle(conn->bundle, rxrpc_bundle_put_conn);
> rxrpc_put_peer(conn->peer, rxrpc_peer_put_conn);
> - rxrpc_put_local(conn->local, rxrpc_local_put_kill_conn);
>
> /* Drain the Rx queue. Note that even though we've unpublished, an
> * incoming packet could still be being added to our Rx queue, so we
> * will need to drain it again in the RCU cleanup handler.
> */
> - rxrpc_purge_queue(&conn->rx_queue);
> + rxrpc_purge_conn_queue(conn);
>
> page_frag_cache_drain(&conn->tx_data_alloc);
> + rxrpc_put_local(conn->local, rxrpc_local_put_kill_conn);
> call_rcu(&conn->rcu, rxrpc_rcu_free_connection);
> }
[Severity: Low]
This isn't a bug introduced by this patch, but is this comment still
accurate? rxrpc_rcu_free_connection() never drains conn->rx_queue. It only
traces, kfree()s the conn and decrements nr_conns.
Late arrivals don't look possible either. The only producer,
rxrpc_post_packet_to_conn(), runs with a reference taken through
rxrpc_get_connection_maybe().
Under the new accounting, a drain in the RCU handler couldn't uncharge
anyway, because rxrpc_put_local() would already have run.
Since rxrpc_put_local() is being moved around this purge anyway, could the
comment be updated as well?
[ ... ]
> diff --git a/net/rxrpc/oob.c b/net/rxrpc/oob.c
> index c80ee2487d094..e3d135b12d22d 100644
> --- a/net/rxrpc/oob.c
> +++ b/net/rxrpc/oob.c
> @@ -46,7 +46,9 @@ bool rxrpc_notify_socket_oob(struct rxrpc_call *call, struct sk_buff *skb)
> sk = &rx->sk;
> spin_lock_irq(&rx->recvmsg_lock);
>
> - if (sk->sk_state < RXRPC_CLOSE) {
> + if (sk->sk_state < RXRPC_CLOSE &&
> + rxrpc_charge_async_queue(sp->chall.conn->local, skb)) {
[Severity: High]
Can one socket use this charge to starve every other user of the same
rxrpc_local?
rx_async_queue_bytes is a single counter per rxrpc_local, capped by the UDP
socket's sk_rcvbuf. It has no per-socket or per-connection split:
rxrpc_charge_async_queue() {
...
queued = atomic_long_read(&local->rx_async_queue_bytes);
if (queued &&
(queued >= rcvbuf ||
(long)skb->truesize >= rcvbuf - queued)) {
sk_drops_inc(sk);
return false;
}
...
}
The connection worker frees conn->rx_queue packets quickly. A managed RXGK
CHALLENGE charged here is different: it stays charged on rx->recvmsg_oobq
and then rx->pending_oobq until the application answers it through
rxrpc_respond_to_oob(), or it is freed by rxrpc_kernel_free_oob() or
rxrpc_purge_oob_queue(). Nothing limits how long these stay or how many
there are.
An unprivileged user can attach to a shared endpoint. rxrpc_bind() with
srx_service == 0 goes through rxrpc_lookup_local(), which reuses any
existing local with a matching transport address. That includes a service
endpoint such as kAFS.
That user could then set RXRPC_MANAGE_RESPONSE, make an RXGK call to a
server they control, and never answer the challenges.
rxgk_validate_challenge() doesn't deduplicate, so every CHALLENGE goes
through rxrpc_post_challenge()->rxrpc_notify_socket_oob() and is charged
here.
After roughly sk_rcvbuf / truesize challenges, rxrpc_charge_async_queue()
fails for every other connection on the local. rxrpc_post_packet_to_conn()
then silently drops their CHALLENGE and RESPONSE packets.
rxrpc_post_challenge() still returns true, and rxrpc_input_conn_packet()
ignores the result for RESPONSE.
Would that leave other sockets' service connections stuck in CHALLENGING,
and their client connections never answering, until timeout? That would
last for as long as the attacker keeps the socket open.
The later patch in this series, "rxrpc: bound DATA receive queues", charges
DATA to the same per-local counter. With the budget pinned, DATA would then
be dropped for every call on the endpoint as well.
The commit message only says both paths are accounted "against a per-local
receive budget" and doesn't mention this trade-off. Would per-socket
accounting, or a separate reservation for OOB challenges, avoid it?
> + skb->mark = RXRPC_SKB_MARK_CHALLENGE;
[Severity: Low]
rxrpc_io_thread() is still processing this skb; the post path only takes an
extra reference with rxrpc_get_skb(). After rxrpc_input_packet() returns,
the I/O thread does:
if (!rxrpc_input_packet(local, skb))
rxrpc_reject_packet(local, skb);
trace_rxrpc_rx_done(skb->mark, skb->priority);
Before this patch the mark stayed 0 on this path. Will the rxrpc_rx_done
tracepoint now report r=2 (RXRPC_SKB_MARK_CHALLENGE) as the receive result
for every queued managed RXGK challenge, instead of 0 or a REJECT code?
> skb->skb_mstamp_ns = rx->oob_id_counter++;
> rxrpc_get_skb(skb, rxrpc_skb_get_post_oob);
> skb_queue_tail(&rx->recvmsg_oobq, skb);
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/cover.1792168462.git.zihanx%40nebusec.ai
^ permalink raw reply [flat|nested] 9+ messages in thread
* [PATCH net v10 3/3] rxrpc: bound DATA receive queues
2026-10-16 19:59 [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion Zihan Xi
2026-10-16 19:59 ` [PATCH net v10 1/3] rxrpc: account encapsulated packets to UDP socket Zihan Xi
2026-10-16 19:59 ` [PATCH net v10 2/3] rxrpc: bound connection and OOB receive queues Zihan Xi
@ 2026-10-16 19:59 ` Zihan Xi
2026-10-20 20:21 ` netdev-bot+sashiko
2026-10-16 20:03 ` [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion netdev-bot+sinfo
2026-10-17 16:32 ` Jakub Kicinski
4 siblings, 1 reply; 9+ messages in thread
From: Zihan Xi @ 2026-10-16 19:59 UTC (permalink / raw)
To: linux-afs, netdev
Cc: dhowells, marc.dionne, davem, edumazet, pabeni, horms, vega, root,
zihanx
DATA packets retained in per-call buffers are not covered by common socket
receive-memory accounting. Separate calls can therefore accumulate more
DATA than the shared limit. The receive window also advances as packets
arrive, while rx_consumed advances only as the application reads them, so a
single call can retain more unconsumed DATA than the window should allow.
Charge accepted DATA to the per-local asynchronous receive budget and base
the per-call receive window on rx_consumed. Account split jumbo skbs and
release the charge when packets are consumed or purged.
Fixes: 17926a79320a ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both")
Fixes: 5d7edbc9231e ("rxrpc: Get rid of the Rx ring")
Cc: stable@vger.kernel.org
Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
Reported-by: Vega <vega@nebusec.ai>
Assisted-by: LLM
Co-developed-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Luxing Yin <root@tr0jan.top>
Signed-off-by: Zihan Xi <zihanx@nebusec.ai>
---
changes in v10:
- Correct the aggregate DATA-accounting Fixes tag to 17926a79320a;
retain 5d7edbc9231e for the receive-window issue.
- v9 Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
changes in v9:
- Split DATA receive-window and per-local aggregate queue accounting
from connection/OOB queue budgeting.
- Attribute the aggregate DATA accounting gap to 248f219cb8bc and retain
5d7edbc9231e for the receive-window accounting issue.
- v8 Link: https://lore.kernel.org/all/cover.1791428529.git.zihanx@nebusec.ai/
---
net/rxrpc/ar-internal.h | 23 ++++++++++-
net/rxrpc/call_event.c | 1 +
net/rxrpc/call_object.c | 4 +-
net/rxrpc/input.c | 83 +++++++++++++++++++++++++------------
net/rxrpc/recvmsg.c | 3 +-
net/rxrpc/skbuff.c | 92 +++++++++++++++++++++++++++++++++++++----
6 files changed, 168 insertions(+), 38 deletions(-)
diff --git a/net/rxrpc/ar-internal.h b/net/rxrpc/ar-internal.h
index 36fc4609181e1..1f1f49eca8e11 100644
--- a/net/rxrpc/ar-internal.h
+++ b/net/rxrpc/ar-internal.h
@@ -50,6 +50,7 @@ struct key_preparsed_payload;
struct rxrpc_connection;
struct rxrpc_txbuf;
struct rxrpc_txqueue;
+struct rxrpc_async_queue_charge;
struct rxgk_context;
/*
@@ -222,6 +223,15 @@ struct rxrpc_host_header {
u16 serviceId; /* service ID */
} __packed;
+/*
+ * Shared asynchronous receive-memory charge for split jumbo DATA packets.
+ */
+struct rxrpc_async_queue_charge {
+ refcount_t ref;
+ long size;
+ bool charged;
+};
+
/*
* RxRPC socket buffer private variables
* - max 48 bytes (struct sk_buff::cb)
@@ -233,6 +243,11 @@ struct rxrpc_skb_priv {
u16 offset; /* Offset of data */
u16 len; /* Length of data */
};
+ struct {
+ u16 offset;
+ u16 len;
+ struct rxrpc_async_queue_charge *charge;
+ } data;
struct {
rxrpc_seq_t first_ack; /* First packet in acks table */
rxrpc_seq_t prev_ack; /* Highest seq seen */
@@ -1517,8 +1532,12 @@ void rxrpc_new_skb(struct sk_buff *, enum rxrpc_skb_trace);
void rxrpc_see_skb(struct sk_buff *, enum rxrpc_skb_trace);
void rxrpc_get_skb(struct sk_buff *, enum rxrpc_skb_trace);
void rxrpc_free_skb(struct sk_buff *, enum rxrpc_skb_trace);
-bool rxrpc_charge_async_queue(struct rxrpc_local *, struct sk_buff *);
-void rxrpc_uncharge_async_queue(struct rxrpc_local *, struct sk_buff *);
+struct rxrpc_async_queue_charge *rxrpc_alloc_async_queue_charge(struct sk_buff *skb);
+void rxrpc_put_async_queue_charge(struct rxrpc_local *local,
+ struct rxrpc_async_queue_charge *charge);
+bool rxrpc_charge_async_queue(struct rxrpc_local *local, struct sk_buff *skb);
+void rxrpc_uncharge_async_queue(struct rxrpc_local *local, struct sk_buff *skb);
+void rxrpc_purge_data_queue(struct rxrpc_call *call, struct sk_buff_head *list);
void rxrpc_purge_queue(struct sk_buff_head *);
/*
diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c
index 21be9c86d7a74..9360852604645 100644
--- a/net/rxrpc/call_event.c
+++ b/net/rxrpc/call_event.c
@@ -473,6 +473,7 @@ bool rxrpc_input_call_event(struct rxrpc_call *call)
out:
if (__rxrpc_call_is_complete(call)) {
timer_delete_sync(&call->timer);
+ rxrpc_purge_queue(&call->rx_queue);
if (!test_bit(RXRPC_CALL_DISCONNECTED, &call->flags))
rxrpc_disconnect_call(call);
if (call->security)
diff --git a/net/rxrpc/call_object.c b/net/rxrpc/call_object.c
index 817ed9acb91e6..8f115be491249 100644
--- a/net/rxrpc/call_object.c
+++ b/net/rxrpc/call_object.c
@@ -553,9 +553,9 @@ static void rxrpc_cleanup_tx_buffers(struct rxrpc_call *call)
*/
static void rxrpc_cleanup_rx_buffers(struct rxrpc_call *call)
{
- rxrpc_purge_queue(&call->recvmsg_queue);
+ rxrpc_purge_data_queue(call, &call->recvmsg_queue);
rxrpc_purge_queue(&call->rx_queue);
- rxrpc_purge_queue(&call->rx_oos_queue);
+ rxrpc_purge_data_queue(call, &call->rx_oos_queue);
kfree(call->rx_dec_buffer);
}
diff --git a/net/rxrpc/input.c b/net/rxrpc/input.c
index 73cafe6bfa9f5..6f0ba50097390 100644
--- a/net/rxrpc/input.c
+++ b/net/rxrpc/input.c
@@ -479,7 +479,9 @@ static void rxrpc_input_queue_data(struct rxrpc_call *call, struct sk_buff *skb,
* Process a DATA packet.
*/
static void rxrpc_input_data_one(struct rxrpc_call *call, struct sk_buff *skb,
- bool *_notify, rxrpc_serial_t *_ack_serial, int *_ack_reason)
+ bool *_notify, rxrpc_serial_t *_ack_serial,
+ int *_ack_reason,
+ struct rxrpc_async_queue_charge **_charge)
{
struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
struct sk_buff *oos;
@@ -487,9 +489,13 @@ static void rxrpc_input_data_one(struct rxrpc_call *call, struct sk_buff *skb,
unsigned int sack = call->ackr_sack_base;
rxrpc_seq_t window = call->ackr_window;
rxrpc_seq_t wtop = call->ackr_wtop;
- rxrpc_seq_t wlimit = window + call->rx_winsize - 1;
+ /* Bound queued DATA by the application's receive window. */
+ rxrpc_seq_t wlimit = smp_load_acquire(&call->rx_consumed) +
+ call->rx_winsize;
rxrpc_seq_t seq = sp->hdr.seq;
bool last = sp->hdr.flags & RXRPC_LAST_PACKET;
+ bool set_last = false;
+ unsigned int slot = 0;
int ack_reason = -1;
rxrpc_inc_stat(call->rxnet, stat_rx_data);
@@ -498,10 +504,24 @@ static void rxrpc_input_data_one(struct rxrpc_call *call, struct sk_buff *skb,
if (sp->hdr.flags & RXRPC_JUMBO_PACKET)
rxrpc_inc_stat(call->rxnet, stat_rx_data_jumbo);
+ if (after(seq, call->rx_highest_seq))
+ call->rx_highest_seq = seq;
+
+ trace_rxrpc_rx_data(call->debug_id, seq, serial, sp->hdr.flags);
+
+ if (after(seq, wlimit)) {
+ ack_reason = RXRPC_ACK_EXCEEDS_WINDOW;
+ goto send_ack;
+ }
+
if (last) {
- if (test_and_set_bit(RXRPC_CALL_RX_LAST, &call->flags) &&
- seq + 1 != wtop)
- return rxrpc_proto_abort(call, seq, rxrpc_eproto_different_last);
+ if (test_and_set_bit(RXRPC_CALL_RX_LAST, &call->flags)) {
+ if (seq + 1 != wtop)
+ return rxrpc_proto_abort(call, seq,
+ rxrpc_eproto_different_last);
+ } else {
+ set_last = true;
+ }
} else {
if (test_bit(RXRPC_CALL_RX_LAST, &call->flags) &&
after_eq(seq, wtop)) {
@@ -511,17 +531,33 @@ static void rxrpc_input_data_one(struct rxrpc_call *call, struct sk_buff *skb,
}
}
- if (after(seq, call->rx_highest_seq))
- call->rx_highest_seq = seq;
-
- trace_rxrpc_rx_data(call->debug_id, seq, serial, sp->hdr.flags);
-
if (before(seq, window)) {
ack_reason = RXRPC_ACK_DUPLICATE;
goto send_ack;
}
- if (after(seq, wlimit)) {
- ack_reason = RXRPC_ACK_EXCEEDS_WINDOW;
+
+ if (seq != window) {
+ slot = seq - window;
+ sack = (sack + slot) % RXRPC_SACK_SIZE;
+
+ if (call->ackr_sack_table[sack]) {
+ ack_reason = RXRPC_ACK_DUPLICATE;
+ goto send_ack;
+ }
+ }
+
+ sp->data.charge = NULL;
+ if (sp->offset != sizeof(struct rxrpc_wire_header) ||
+ (sp->hdr.flags & RXRPC_JUMBO_PACKET)) {
+ if (!*_charge)
+ *_charge = rxrpc_alloc_async_queue_charge(skb);
+ sp->data.charge = *_charge;
+ }
+ if (!rxrpc_charge_async_queue(call->local, skb)) {
+ sp->data.charge = NULL;
+ if (set_last)
+ clear_bit(RXRPC_CALL_RX_LAST, &call->flags);
+ ack_reason = RXRPC_ACK_NOSPACE;
goto send_ack;
}
@@ -568,19 +604,8 @@ static void rxrpc_input_data_one(struct rxrpc_call *call, struct sk_buff *skb,
call->ackr_sack_base = sack;
} else {
- unsigned int slot;
-
ack_reason = RXRPC_ACK_OUT_OF_SEQUENCE;
-
- slot = seq - window;
- sack = (sack + slot) % RXRPC_SACK_SIZE;
-
- if (call->ackr_sack_table[sack % RXRPC_SACK_SIZE]) {
- ack_reason = RXRPC_ACK_DUPLICATE;
- goto send_ack;
- }
-
- call->ackr_sack_table[sack % RXRPC_SACK_SIZE] |= 1;
+ call->ackr_sack_table[sack] |= 1;
trace_rxrpc_sack(call, seq, sack, rxrpc_sack_oos);
if (after(seq + 1, wtop)) {
@@ -623,6 +648,7 @@ static void rxrpc_input_data_one(struct rxrpc_call *call, struct sk_buff *skb,
*/
static bool rxrpc_input_split_jumbo(struct rxrpc_call *call, struct sk_buff *skb)
{
+ struct rxrpc_async_queue_charge *charge = NULL;
struct rxrpc_jumbo_header jhdr;
struct rxrpc_skb_priv *sp = rxrpc_skb(skb), *jsp;
struct sk_buff *jskb;
@@ -644,13 +670,15 @@ static bool rxrpc_input_split_jumbo(struct rxrpc_call *call, struct sk_buff *skb
jskb = skb_clone(skb, GFP_NOFS);
if (!jskb) {
kdebug("couldn't clone");
+ rxrpc_put_async_queue_charge(call->local, charge);
return false;
}
rxrpc_new_skb(jskb, rxrpc_skb_new_jumbo_subpacket);
jsp = rxrpc_skb(jskb);
jsp->offset = offset;
jsp->len = RXRPC_JUMBO_DATALEN;
- rxrpc_input_data_one(call, jskb, ¬ify, &ack_serial, &ack_reason);
+ rxrpc_input_data_one(call, jskb, ¬ify, &ack_serial, &ack_reason,
+ &charge);
rxrpc_free_skb(jskb, rxrpc_skb_put_jumbo_subpacket);
sp->hdr.flags = jhdr.flags;
@@ -664,7 +692,8 @@ static bool rxrpc_input_split_jumbo(struct rxrpc_call *call, struct sk_buff *skb
sp->offset = offset;
sp->len = len;
- rxrpc_input_data_one(call, skb, ¬ify, &ack_serial, &ack_reason);
+ rxrpc_input_data_one(call, skb, ¬ify, &ack_serial, &ack_reason,
+ &charge);
stat_ix = umin(count, ARRAY_SIZE(call->rxnet->stat_rx_jumbo)) - 1;
atomic_inc(&call->rxnet->stat_rx_jumbo[stat_ix]);
@@ -681,9 +710,11 @@ static bool rxrpc_input_split_jumbo(struct rxrpc_call *call, struct sk_buff *skb
trace_rxrpc_notify_socket(call->debug_id, sp->hdr.serial);
rxrpc_notify_socket(call);
}
+ rxrpc_put_async_queue_charge(call->local, charge);
return true;
protocol_error:
+ rxrpc_put_async_queue_charge(call->local, charge);
return false;
}
diff --git a/net/rxrpc/recvmsg.c b/net/rxrpc/recvmsg.c
index 172c72944cddd..85c9562cb3790 100644
--- a/net/rxrpc/recvmsg.c
+++ b/net/rxrpc/recvmsg.c
@@ -118,6 +118,7 @@ static void rxrpc_rotate_rx_window(struct rxrpc_call *call)
skb = skb_dequeue(&call->recvmsg_queue);
rxrpc_see_skb(skb, rxrpc_skb_see_rotate);
+ rxrpc_uncharge_async_queue(call->local, skb);
sp = rxrpc_skb(skb);
tseq = sp->hdr.seq;
@@ -565,7 +566,7 @@ int rxrpc_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
goto not_yet_complete;
call_failed:
- rxrpc_purge_queue(&call->recvmsg_queue);
+ rxrpc_purge_data_queue(call, &call->recvmsg_queue);
call_complete:
ret = rxrpc_recvmsg_term(call, msg);
if (ret < 0)
diff --git a/net/rxrpc/skbuff.c b/net/rxrpc/skbuff.c
index a5113a284456e..3b999200b6fc6 100644
--- a/net/rxrpc/skbuff.c
+++ b/net/rxrpc/skbuff.c
@@ -10,6 +10,7 @@
#include <linux/module.h>
#include <linux/net.h>
#include <linux/skbuff.h>
+#include <linux/slab.h>
#include <net/sock.h>
#include <net/af_rxrpc.h>
#include "ar-internal.h"
@@ -58,10 +59,7 @@ void rxrpc_free_skb(struct sk_buff *skb, enum rxrpc_skb_trace why)
}
}
-/*
- * Charge an skb retained outside UDP receive-memory accounting.
- */
-bool rxrpc_charge_async_queue(struct rxrpc_local *local, struct sk_buff *skb)
+static bool rxrpc_charge_async_queue_bytes(struct rxrpc_local *local, long size)
{
struct sock *sk = local->socket->sk;
long rcvbuf;
@@ -72,25 +70,105 @@ bool rxrpc_charge_async_queue(struct rxrpc_local *local, struct sk_buff *skb)
for (;;) {
queued = atomic_long_read(&local->rx_async_queue_bytes);
if (queued &&
- (queued >= rcvbuf ||
- (long)skb->truesize >= rcvbuf - queued)) {
+ (queued >= rcvbuf || size >= rcvbuf - queued)) {
sk_drops_inc(sk);
return false;
}
if (atomic_long_cmpxchg(&local->rx_async_queue_bytes, queued,
- queued + (long)skb->truesize) == queued)
+ queued + size) == queued)
return true;
}
}
+struct rxrpc_async_queue_charge *rxrpc_alloc_async_queue_charge(struct sk_buff *skb)
+{
+ struct rxrpc_async_queue_charge *charge;
+
+ charge = kzalloc(sizeof(*charge), GFP_NOFS);
+ if (!charge)
+ return NULL;
+
+ refcount_set(&charge->ref, 1);
+ charge->size = skb->truesize + sizeof(*charge);
+ return charge;
+}
+
+void rxrpc_put_async_queue_charge(struct rxrpc_local *local,
+ struct rxrpc_async_queue_charge *charge)
+{
+ if (charge && refcount_dec_and_test(&charge->ref)) {
+ if (charge->charged)
+ atomic_long_sub(charge->size, &local->rx_async_queue_bytes);
+ kfree(charge);
+ }
+}
+
+/*
+ * Charge an skb retained outside UDP receive-memory accounting.
+ */
+bool rxrpc_charge_async_queue(struct rxrpc_local *local, struct sk_buff *skb)
+{
+ struct rxrpc_async_queue_charge *charge = NULL;
+ struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+ long size = 0;
+
+ if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA)
+ charge = sp->data.charge;
+
+ if (charge) {
+ bool clone = sp->hdr.flags & RXRPC_JUMBO_PACKET;
+
+ if (!charge->charged) {
+ size = charge->size;
+ if (clone)
+ size += SKB_DATA_ALIGN(sizeof(struct sk_buff));
+ } else if (clone) {
+ size = SKB_DATA_ALIGN(sizeof(struct sk_buff));
+ }
+ if (size && !rxrpc_charge_async_queue_bytes(local, size))
+ return false;
+ charge->charged = true;
+ refcount_inc(&charge->ref);
+ return true;
+ }
+ return rxrpc_charge_async_queue_bytes(local, (long)skb->truesize);
+}
+
/*
* Release the asynchronous receive charge on an skb.
*/
void rxrpc_uncharge_async_queue(struct rxrpc_local *local, struct sk_buff *skb)
{
+ struct rxrpc_async_queue_charge *charge;
+ struct rxrpc_skb_priv *sp = rxrpc_skb(skb);
+
+ if (sp->hdr.type == RXRPC_PACKET_TYPE_DATA) {
+ charge = sp->data.charge;
+ if (charge) {
+ sp->data.charge = NULL;
+ if (sp->hdr.flags & RXRPC_JUMBO_PACKET)
+ atomic_long_sub(SKB_DATA_ALIGN(sizeof(struct sk_buff)),
+ &local->rx_async_queue_bytes);
+ rxrpc_put_async_queue_charge(local, charge);
+ return;
+ }
+ }
atomic_long_sub(skb->truesize, &local->rx_async_queue_bytes);
}
+/*
+ * Purge DATA from a call's receive queue and release its per-local charge.
+ */
+void rxrpc_purge_data_queue(struct rxrpc_call *call, struct sk_buff_head *list)
+{
+ struct sk_buff *skb;
+
+ while ((skb = skb_dequeue(list)) != NULL) {
+ rxrpc_uncharge_async_queue(call->local, skb);
+ rxrpc_free_skb(skb, rxrpc_skb_put_purge);
+ }
+}
+
/*
* Clear a queue of socket buffers.
*/
--
2.43.0
^ permalink raw reply related [flat|nested] 9+ messages in thread* Re: [PATCH net v10 3/3] rxrpc: bound DATA receive queues
2026-10-16 19:59 ` [PATCH net v10 3/3] rxrpc: bound DATA " Zihan Xi
@ 2026-10-20 20:21 ` netdev-bot+sashiko
0 siblings, 0 replies; 9+ messages in thread
From: netdev-bot+sashiko @ 2026-10-20 20:21 UTC (permalink / raw)
To: zihanx
Cc: linux-afs, netdev, dhowells, marc.dionne, davem, edumazet, pabeni,
horms, vega, root, kuba
Thank you for your contribution! Sashiko AI review found 3 potential
issue(s) to consider.
Critical: 0 · High: 2 · Medium: 1 · Low: 0
If you believe none of the issues are legitimate, please reply to each
issue of Medium severity or higher and explain why the AI is wrong.
If any of them are legitimate and you plan to respin, please let patchwork
know by including "pw-bot: cr" as a separate line at the end of your reply
(one such reply per series is enough).
- [High] rxrpc_input_data_one() (net/rxrpc/input.c:556) now charges every
accepted DATA skb, from every call on a local endpoint, to one…
- [High] Out-of-sequence DATA can deadlock a call.
- [Medium] When a call completes, the DATA skbs on call->rx_oos_queue
keep their charge against the shared per-local budget until…
--
rxrpc: bound DATA receive queues
This patch charges accepted DATA skbs, including split jumbo subpackets,
to the per-local asynchronous receive budget (local->rx_async_queue_bytes).
The charge is released when recvmsg consumes the packets or when they are
purged. The per-call receive window limit is now based on rx_consumed
instead of ackr_window.
Link: https://lore.kernel.org/all/cover.1791994057.git.zihanx@nebusec.ai/
> diff --git a/net/rxrpc/call_event.c b/net/rxrpc/call_event.c
> index 21be9c86d7a74..9360852604645 100644
> --- a/net/rxrpc/call_event.c
> +++ b/net/rxrpc/call_event.c
> @@ -473,6 +473,7 @@ bool rxrpc_input_call_event(struct rxrpc_call *call)
> out:
> if (__rxrpc_call_is_complete(call)) {
> timer_delete_sync(&call->timer);
> + rxrpc_purge_queue(&call->rx_queue);
[Severity: Medium]
Should this purge rx_oos_queue instead of rx_queue?
Nothing on call->rx_queue is charged, and rxrpc_purge_queue() only
uncharges RXRPC_SKB_MARK_SERVICE_CONN_SECURED skbs.
The DATA skbs on call->rx_oos_queue were charged in rxrpc_input_data_one().
Once the call is complete they can never be delivered, because
rxrpc_input_data() returns early for completed calls.
Those charges are only released in
rxrpc_destroy_call()->rxrpc_cleanup_rx_buffers():
rxrpc_purge_data_queue(call, &call->rx_oos_queue);
Suppose a peer fills rx_oos_queue and then aborts the call. Does the charge
stay held against the shared local budget until the application reads the
termination and releases the call, or until a kernel user drops its last
reference?
Only the I/O thread and the destroy path touch rx_oos_queue, so it looks
safe to purge it here with rxrpc_purge_data_queue().
> if (!test_bit(RXRPC_CALL_DISCONNECTED, &call->flags))
> rxrpc_disconnect_call(call);
[ ... ]
> diff --git a/net/rxrpc/input.c b/net/rxrpc/input.c
> index 73cafe6bfa9f5..6f0ba50097390 100644
> --- a/net/rxrpc/input.c
> +++ b/net/rxrpc/input.c
[ ... ]
> @@ -511,17 +531,33 @@ static void rxrpc_input_data_one(struct rxrpc_call *call, struct sk_buff *skb,
[ ... ]
> + sp->data.charge = NULL;
> + if (sp->offset != sizeof(struct rxrpc_wire_header) ||
> + (sp->hdr.flags & RXRPC_JUMBO_PACKET)) {
> + if (!*_charge)
> + *_charge = rxrpc_alloc_async_queue_charge(skb);
> + sp->data.charge = *_charge;
> + }
> + if (!rxrpc_charge_async_queue(call->local, skb)) {
> + sp->data.charge = NULL;
> + if (set_last)
> + clear_bit(RXRPC_CALL_RX_LAST, &call->flags);
> + ack_reason = RXRPC_ACK_NOSPACE;
> goto send_ack;
> }
[Severity: High]
Can one call use up the receive budget for the whole local endpoint here?
Every accepted DATA skb from every call on the rxrpc_local is now charged
to local->rx_async_queue_bytes. rxrpc_charge_async_queue_bytes() caps that
counter at the UDP socket's sk_rcvbuf:
net/rxrpc/skbuff.c:rxrpc_charge_async_queue_bytes() {
...
if (queued &&
(queued >= rcvbuf || size >= rcvbuf - queued)) {
sk_drops_inc(sk);
return false;
}
...
}
rxrpc_adjust_rcvbuf() sizes sk_rcvbuf for about one call's window and
clamps it to sysctl_rmem_max (212992 by default). That is room for about
90-100 normal DATA skbs across the whole endpoint.
Meanwhile rxrpc_fill_out_ack() still advertises up to rx_winsize (255)
packets to each peer:
net/rxrpc/output.c:rxrpc_fill_out_ack() {
...
qsize = (window - 1) - call->rx_consumed;
rsize = max_t(int, call->rx_winsize - qsize, 0);
...
}
The charge is released only when the application reads the data, in
rxrpc_rotate_rx_window(), or when the call is destroyed.
A remote peer could open a call to a listening service, for example kafs on
AFS_CM_PORT using its shared socket. It sends seq 1, then seq 3..N, and
never seq 2. Each of those packets is charged and queued on rx_oos_queue,
all within wlimit.
No authentication seems to be needed, since DATA is queued even while the
connection is still challenging. Each packet also refreshes expect_req_by,
so the call stays alive. A slow reader on a single call could have the same
effect.
Once the budget is full, would every other call on the endpoint get
RXRPC_ACK_NOSPACE drops?
The same counter also gates CHALLENGE/RESPONSE posting in
rxrpc_post_packet_to_conn() and OOB CHALLENGE delivery. Would connection
security setup for unrelated connections fail as well?
Before this patch DATA wasn't charged, so other calls couldn't be starved
this way. Does this need a per-call or per-peer split of the budget, or a
reserved portion? The commit message also doesn't mention that calls now
share this limit.
[Severity: High]
Can ordinary packet loss livelock a call through this check?
The charge is applied before the seq == window test. The packet that would
fill the hole therefore goes through the same budget check as
out-of-sequence packets, with no exemption or reserved space.
With the defaults, rxrpc_rx_window_size is 255 and the budget is clamped to
rmem_max, roughly 90-100 skbs at a typical truesize. A Linux sender can
have up to RXRPC_TX_MAX_WINDOW (128) packets in flight.
Suppose the packet at seq == window is lost early in a large flight. The
packets after it are charged and queued on rx_oos_queue until the budget is
nearly full.
Every retransmission of the missing packet would then fail here and be
dropped with RXRPC_ACK_NOSPACE. The OOS packets move to recvmsg_queue only
after the hole is filled:
while ((oos = skb_peek(&call->rx_oos_queue))) {
rxrpc_recvmsg_data() only reads recvmsg_queue, so the application can't
release anything either.
Does anything break this cycle other than call destruction? The
retransmissions keep refreshing the call timers. The call could then stall
indefinitely while holding the shared budget, which also starves the other
calls on the same rxrpc_local.
[ ... ]
--
Sashiko AI review · https://netdev-ai.bots.linux.dev/sashiko/#/patchset/cover.1792168462.git.zihanx%40nebusec.ai
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion
2026-10-16 19:59 [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion Zihan Xi
` (2 preceding siblings ...)
2026-10-16 19:59 ` [PATCH net v10 3/3] rxrpc: bound DATA " Zihan Xi
@ 2026-10-16 20:03 ` netdev-bot+sinfo
2026-10-16 20:07 ` zihan xi
2026-10-17 16:32 ` Jakub Kicinski
4 siblings, 1 reply; 9+ messages in thread
From: netdev-bot+sinfo @ 2026-10-16 20:03 UTC (permalink / raw)
To: Zihan Xi
Cc: linux-afs, netdev, dhowells, marc.dionne, davem, edumazet, pabeni,
horms, vega, root
Hi!
This is an automated message. This series looks like a fix, but its
commit messages seem to be missing some information:
- How the issue was discovered, e.g. hit in production, hit during
development, syzbot report, manual code inspection, LLM or static
analysis tool scan.
Please do not repost the series just to address the above. Instead,
reply to this email with the missing information, so that reviewers
can take it into account. If the series needs another revision for
other reasons, please include the information in the commit messages
then.
The evaluation is done by an LLM so it may be wrong, if you think
that is the case please reply and explain.
^ permalink raw reply [flat|nested] 9+ messages in thread* Re: [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion
2026-10-16 20:03 ` [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion netdev-bot+sinfo
@ 2026-10-16 20:07 ` zihan xi
0 siblings, 0 replies; 9+ messages in thread
From: zihan xi @ 2026-10-16 20:07 UTC (permalink / raw)
To: netdev-bot+sinfo
Cc: linux-afs, netdev, dhowells, marc.dionne, davem, edumazet, pabeni,
horms, vega, root
On Sat, Oct 17, 2026 at 4:03 AM <netdev-bot+sinfo@kernel.org> wrote:
>
> Hi!
>
> This is an automated message. This series looks like a fix, but its
> commit messages seem to be missing some information:
>
> - How the issue was discovered, e.g. hit in production, hit during
> development, syzbot report, manual code inspection, LLM or static
> analysis tool scan.
>
> Please do not repost the series just to address the above. Instead,
> reply to this email with the missing information, so that reviewers
> can take it into account. If the series needs another revision for
> other reasons, please include the information in the commit messages
> then.
>
> The evaluation is done by an LLM so it may be wrong, if you think
> that is the case please reply and explain.
The issue was found during development while investigating RxRPC
receive-memory exhaustion. Code inspection identified encapsulated
skbs being queued without effective receive-memory accounting, and a
PoC reproduced the resulting OOM on an unpatched net/main baseline.
This was not a production or syzbot report.
^ permalink raw reply [flat|nested] 9+ messages in thread
* Re: [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion
2026-10-16 19:59 [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion Zihan Xi
` (3 preceding siblings ...)
2026-10-16 20:03 ` [PATCH net v10 0/3] rxrpc: fix encap_rcv skb accounting exhaustion netdev-bot+sinfo
@ 2026-10-17 16:32 ` Jakub Kicinski
4 siblings, 0 replies; 9+ messages in thread
From: Jakub Kicinski @ 2026-10-17 16:32 UTC (permalink / raw)
To: Zihan Xi
Cc: linux-afs, netdev, dhowells, marc.dionne, davem, edumazet, pabeni,
horms, vega, root
On Fri, 16 Oct 2026 19:59:50 +0000 Zihan Xi wrote:
> Hi Linux kernel maintainers,
coccicheck says:
net/rxrpc/oob.c:281:21-25: Unneeded variable: "type". Return "RXRPC_OOB_CHALLENGE" on line 295
Please don't CC netdev@ on v11, linux-afs@ is enough.
^ permalink raw reply [flat|nested] 9+ messages in thread