| From stable+bounces-240394-greg=kroah.com@vger.kernel.org Thu Apr 23 00:25:52 2026 |
| From: Jay Wang <wanjay@amazon.com> |
| Date: Wed, 22 Apr 2026 22:24:32 +0000 |
| Subject: rxrpc: Fix recvmsg() unconditional requeue |
| To: <stable@vger.kernel.org> |
| Cc: <dhowells@redhat.com>, <marc.dionne@auristor.com>, <davem@davemloft.net>, <edumazet@google.com>, <kuba@kernel.org>, <pabeni@redhat.com>, <netdev@vger.kernel.org>, <linux-afs@lists.infradead.org>, <jay.wang.upstream@gmail.com>, Faith <faith@zellic.io>, Pumpkin Chang <pumpkin@devco.re> |
| Message-ID: <20260422222432.7211-1-wanjay@amazon.com> |
| |
| From: David Howells <dhowells@redhat.com> |
| |
| [ Upstream commit 2c28769a51deb6022d7fbd499987e237a01dd63a ] |
| |
| If rxrpc_recvmsg() fails because MSG_DONTWAIT was specified but the call |
| at the front of the recvmsg queue already has its mutex locked, it |
| requeues the call - whether or not the call is already queued. The call |
| may be on the queue because MSG_PEEK was also passed and so the call was |
| not dequeued or because the I/O thread requeued it. |
| |
| The unconditional requeue may then corrupt the recvmsg queue, leading to |
| things like UAFs or refcount underruns. |
| |
| Fix this by only requeuing the call if it isn't already on the queue - |
| and moving it to the front if it is already queued. If we don't queue |
| it, we have to put the ref we obtained by dequeuing it. |
| |
| Also, MSG_PEEK doesn't dequeue the call so shouldn't call |
| rxrpc_notify_socket() for the call if we didn't use up all the data on |
| the queue, so fix that also. |
| |
| Fixes: 540b1c48c37a ("rxrpc: Fix deadlock between call creation and sendmsg/recvmsg") |
| Reported-by: Faith <faith@zellic.io> |
| Reported-by: Pumpkin Chang <pumpkin@devco.re> |
| Signed-off-by: David Howells <dhowells@redhat.com> |
| Acked-by: Marc Dionne <marc.dionne@auristor.com> |
| Signed-off-by: Jakub Kicinski <kuba@kernel.org> |
| Cc: stable@vger.kernel.org |
| [Adapted to 5.15: use write_lock_bh/write_unlock_bh, trace_rxrpc_call |
| directly for see-call tracing, 5.15 trace enum naming convention, and |
| added entries to both plain enum and EM() macro list.] |
| Signed-off-by: Jay Wang <wanjay@amazon.com> |
| Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org> |
| --- |
| include/trace/events/rxrpc.h | 8 ++++++++ |
| net/rxrpc/recvmsg.c | 22 ++++++++++++++++++---- |
| 2 files changed, 26 insertions(+), 4 deletions(-) |
| |
| --- a/include/trace/events/rxrpc.h |
| +++ b/include/trace/events/rxrpc.h |
| @@ -93,9 +93,13 @@ enum rxrpc_call_trace { |
| rxrpc_call_put_notimer, |
| rxrpc_call_put_timer, |
| rxrpc_call_put_userid, |
| + rxrpc_call_put_recvmsg_peek_nowait, |
| rxrpc_call_queued, |
| rxrpc_call_queued_ref, |
| rxrpc_call_release, |
| + rxrpc_call_see_recvmsg_requeue, |
| + rxrpc_call_see_recvmsg_requeue_first, |
| + rxrpc_call_see_recvmsg_requeue_move, |
| rxrpc_call_seen, |
| }; |
| |
| @@ -291,9 +295,13 @@ enum rxrpc_tx_point { |
| EM(rxrpc_call_put_notimer, "PnT") \ |
| EM(rxrpc_call_put_timer, "PTM") \ |
| EM(rxrpc_call_put_userid, "Pus") \ |
| + EM(rxrpc_call_put_recvmsg_peek_nowait, "PpN") \ |
| EM(rxrpc_call_queued, "QUE") \ |
| EM(rxrpc_call_queued_ref, "QUR") \ |
| EM(rxrpc_call_release, "RLS") \ |
| + EM(rxrpc_call_see_recvmsg_requeue, "SrQ") \ |
| + EM(rxrpc_call_see_recvmsg_requeue_first,"SrF") \ |
| + EM(rxrpc_call_see_recvmsg_requeue_move, "SrM") \ |
| E_(rxrpc_call_seen, "SEE") |
| |
| #define rxrpc_transmit_traces \ |
| --- a/net/rxrpc/recvmsg.c |
| +++ b/net/rxrpc/recvmsg.c |
| @@ -607,7 +607,8 @@ try_again: |
| |
| if (after(call->rx_top, call->rx_hard_ack) && |
| call->rxtx_buffer[(call->rx_hard_ack + 1) & RXRPC_RXTX_BUFF_MASK]) |
| - rxrpc_notify_socket(call); |
| + if (!(flags & MSG_PEEK)) |
| + rxrpc_notify_socket(call); |
| break; |
| default: |
| ret = 0; |
| @@ -642,11 +643,24 @@ error_unlock_call: |
| error_requeue_call: |
| if (!(flags & MSG_PEEK)) { |
| write_lock_bh(&rx->recvmsg_lock); |
| - list_add(&call->recvmsg_link, &rx->recvmsg_q); |
| - write_unlock_bh(&rx->recvmsg_lock); |
| + if (list_empty(&call->recvmsg_link)) { |
| + list_add(&call->recvmsg_link, &rx->recvmsg_q); |
| + trace_rxrpc_call(call->debug_id, |
| + rxrpc_call_see_recvmsg_requeue, |
| + refcount_read(&call->ref), |
| + __builtin_return_address(0), NULL); |
| + write_unlock_bh(&rx->recvmsg_lock); |
| + } else if (list_is_first(&call->recvmsg_link, &rx->recvmsg_q)) { |
| + write_unlock_bh(&rx->recvmsg_lock); |
| + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_first); |
| + } else { |
| + list_move(&call->recvmsg_link, &rx->recvmsg_q); |
| + write_unlock_bh(&rx->recvmsg_lock); |
| + rxrpc_put_call(call, rxrpc_call_see_recvmsg_requeue_move); |
| + } |
| trace_rxrpc_recvmsg(call, rxrpc_recvmsg_requeue, 0, 0, 0, 0); |
| } else { |
| - rxrpc_put_call(call, rxrpc_call_put); |
| + rxrpc_put_call(call, rxrpc_call_put_recvmsg_peek_nowait); |
| } |
| error_no_call: |
| release_sock(&rx->sk); |