mirror of https://github.com/torvalds/linux.git
rxrpc: Fix notification vs call-release vs recvmsg
When a call is released, rxrpc takes the spinlock and removes it from
->recvmsg_q in an effort to prevent racing recvmsg() invocations from
seeing the same call. Now, rxrpc_recvmsg() only takes the spinlock when
actually removing a call from the queue; it doesn't, however, take it in
the lead up to that when it checks to see if the queue is empty. It *does*
hold the socket lock, which prevents a recvmsg/recvmsg race - but this
doesn't prevent sendmsg from ending the call because sendmsg() drops the
socket lock and relies on the call->user_mutex.
Fix this by firstly removing the bit in rxrpc_release_call() that dequeues
the released call and, instead, rely on recvmsg() to simply discard
released calls (done in a preceding fix).
Secondly, rxrpc_notify_socket() is abandoned if the call is already marked
as released rather than trying to be clever by setting both pointers in
call->recvmsg_link to NULL to trick list_empty(). This isn't perfect and
can still race, resulting in a released call on the queue, but recvmsg()
will now clean that up.
Fixes: 17926a7932 ("[AF_RXRPC]: Provide secure RxRPC sockets for use by userspace and kernel both")
Signed-off-by: David Howells <dhowells@redhat.com>
Reviewed-by: Jeffrey Altman <jaltman@auristor.com>
cc: Marc Dionne <marc.dionne@auristor.com>
cc: Junvyyang, Tencent Zhuque Lab <zhuque@tencent.com>
cc: LePremierHomme <kwqcheii@proton.me>
cc: Simon Horman <horms@kernel.org>
cc: linux-afs@lists.infradead.org
Link: https://patch.msgid.link/20250717074350.3767366-4-dhowells@redhat.com
Signed-off-by: Jakub Kicinski <kuba@kernel.org>
This commit is contained in:
parent
962fb1f651
commit
2fd895842d
|
|
@ -322,10 +322,10 @@
|
|||
EM(rxrpc_call_put_kernel, "PUT kernel ") \
|
||||
EM(rxrpc_call_put_poke, "PUT poke ") \
|
||||
EM(rxrpc_call_put_recvmsg, "PUT recvmsg ") \
|
||||
EM(rxrpc_call_put_release_recvmsg_q, "PUT rls-rcmq") \
|
||||
EM(rxrpc_call_put_release_sock, "PUT rls-sock") \
|
||||
EM(rxrpc_call_put_release_sock_tba, "PUT rls-sk-a") \
|
||||
EM(rxrpc_call_put_sendmsg, "PUT sendmsg ") \
|
||||
EM(rxrpc_call_put_unnotify, "PUT unnotify") \
|
||||
EM(rxrpc_call_put_userid_exists, "PUT u-exists") \
|
||||
EM(rxrpc_call_put_userid, "PUT user-id ") \
|
||||
EM(rxrpc_call_see_accept, "SEE accept ") \
|
||||
|
|
@ -338,6 +338,7 @@
|
|||
EM(rxrpc_call_see_disconnected, "SEE disconn ") \
|
||||
EM(rxrpc_call_see_distribute_error, "SEE dist-err") \
|
||||
EM(rxrpc_call_see_input, "SEE input ") \
|
||||
EM(rxrpc_call_see_notify_released, "SEE nfy-rlsd") \
|
||||
EM(rxrpc_call_see_recvmsg, "SEE recvmsg ") \
|
||||
EM(rxrpc_call_see_release, "SEE release ") \
|
||||
EM(rxrpc_call_see_userid_exists, "SEE u-exists") \
|
||||
|
|
|
|||
|
|
@ -561,7 +561,7 @@ static void rxrpc_cleanup_rx_buffers(struct rxrpc_call *call)
|
|||
void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call)
|
||||
{
|
||||
struct rxrpc_connection *conn = call->conn;
|
||||
bool put = false, putu = false;
|
||||
bool putu = false;
|
||||
|
||||
_enter("{%d,%d}", call->debug_id, refcount_read(&call->ref));
|
||||
|
||||
|
|
@ -573,23 +573,13 @@ void rxrpc_release_call(struct rxrpc_sock *rx, struct rxrpc_call *call)
|
|||
|
||||
rxrpc_put_call_slot(call);
|
||||
|
||||
/* Make sure we don't get any more notifications */
|
||||
/* Note that at this point, the call may still be on or may have been
|
||||
* added back on to the socket receive queue. recvmsg() must discard
|
||||
* released calls. The CALL_RELEASED flag should prevent further
|
||||
* notifications.
|
||||
*/
|
||||
spin_lock_irq(&rx->recvmsg_lock);
|
||||
|
||||
if (!list_empty(&call->recvmsg_link)) {
|
||||
_debug("unlinking once-pending call %p { e=%lx f=%lx }",
|
||||
call, call->events, call->flags);
|
||||
list_del(&call->recvmsg_link);
|
||||
put = true;
|
||||
}
|
||||
|
||||
/* list_empty() must return false in rxrpc_notify_socket() */
|
||||
call->recvmsg_link.next = NULL;
|
||||
call->recvmsg_link.prev = NULL;
|
||||
|
||||
spin_unlock_irq(&rx->recvmsg_lock);
|
||||
if (put)
|
||||
rxrpc_put_call(call, rxrpc_call_put_unnotify);
|
||||
|
||||
write_lock(&rx->call_lock);
|
||||
|
||||
|
|
@ -638,6 +628,12 @@ void rxrpc_release_calls_on_socket(struct rxrpc_sock *rx)
|
|||
rxrpc_put_call(call, rxrpc_call_put_release_sock);
|
||||
}
|
||||
|
||||
while ((call = list_first_entry_or_null(&rx->recvmsg_q,
|
||||
struct rxrpc_call, recvmsg_link))) {
|
||||
list_del_init(&call->recvmsg_link);
|
||||
rxrpc_put_call(call, rxrpc_call_put_release_recvmsg_q);
|
||||
}
|
||||
|
||||
_leave("");
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -29,6 +29,10 @@ void rxrpc_notify_socket(struct rxrpc_call *call)
|
|||
|
||||
if (!list_empty(&call->recvmsg_link))
|
||||
return;
|
||||
if (test_bit(RXRPC_CALL_RELEASED, &call->flags)) {
|
||||
rxrpc_see_call(call, rxrpc_call_see_notify_released);
|
||||
return;
|
||||
}
|
||||
|
||||
rcu_read_lock();
|
||||
|
||||
|
|
|
|||
Loading…
Reference in New Issue