rxrpc: Fix potential race in error handling in afs_make_call()

If the rxrpc call set up by afs_make_call() receives an error whilst it is
transmitting the request, there's the possibility that it may get to the
point the rxrpc call is ended (after the error_kill_call label) just as the
call is queued for async processing.

This could manifest itself as call->rxcall being seen as NULL in
afs_deliver_to_call() when it tries to lock the call.

Fix this by splitting rxrpc_kernel_end_call() into a function to shut down
an rxrpc call and a function to release the caller's reference and calling
the latter only when we get to afs_put_call().

Reported-by: Jeffrey Altman <jaltman@auristor.com>
Signed-off-by: David Howells <dhowells@redhat.com>
Tested-by: kafs-testing+fedora36_64checkkafs-build-306@auristor.com
cc: Marc Dionne <marc.dionne@auristor.com>
cc: "David S. Miller" <davem@davemloft.net>
cc: Eric Dumazet <edumazet@google.com>
cc: Jakub Kicinski <kuba@kernel.org>
cc: Paolo Abeni <pabeni@redhat.com>
cc: linux-afs@lists.infradead.org
cc: netdev@vger.kernel.org
Signed-off-by: David S. Miller <davem@davemloft.net>
This commit is contained in:
David Howells 2023-04-21 23:03:46 +01:00 committed by David S. Miller
parent 92ce288ccb
commit e0416e7d33
5 changed files with 44 additions and 23 deletions

View File

@ -848,14 +848,21 @@ The kernel interface functions are as follows:
returned. The caller now holds a reference on this and it must be returned. The caller now holds a reference on this and it must be
properly ended. properly ended.
(#) End a client call:: (#) Shut down a client call::
void rxrpc_kernel_end_call(struct socket *sock, void rxrpc_kernel_shutdown_call(struct socket *sock,
struct rxrpc_call *call);
This is used to shut down a previously begun call. The user_call_ID is
expunged from AF_RXRPC's knowledge and will not be seen again in
association with the specified call.
(#) Release the ref on a client call::
void rxrpc_kernel_put_call(struct socket *sock,
struct rxrpc_call *call); struct rxrpc_call *call);
This is used to end a previously begun call. The user_call_ID is expunged This is used to release the caller's ref on an rxrpc call.
from AF_RXRPC's knowledge and will not be seen again in association with
the specified call.
(#) Send data through a call:: (#) Send data through a call::

View File

@ -179,7 +179,8 @@ void afs_put_call(struct afs_call *call)
ASSERT(call->type->name != NULL); ASSERT(call->type->name != NULL);
if (call->rxcall) { if (call->rxcall) {
rxrpc_kernel_end_call(net->socket, call->rxcall); rxrpc_kernel_shutdown_call(net->socket, call->rxcall);
rxrpc_kernel_put_call(net->socket, call->rxcall);
call->rxcall = NULL; call->rxcall = NULL;
} }
if (call->type->destructor) if (call->type->destructor)
@ -420,10 +421,8 @@ error_kill_call:
* The call, however, might be queued on afs_async_calls and we need to * The call, however, might be queued on afs_async_calls and we need to
* make sure we don't get any more notifications that might requeue it. * make sure we don't get any more notifications that might requeue it.
*/ */
if (call->rxcall) { if (call->rxcall)
rxrpc_kernel_end_call(call->net->socket, call->rxcall); rxrpc_kernel_shutdown_call(call->net->socket, call->rxcall);
call->rxcall = NULL;
}
if (call->async) { if (call->async) {
if (cancel_work_sync(&call->async_work)) if (cancel_work_sync(&call->async_work))
afs_put_call(call); afs_put_call(call);

View File

@ -57,7 +57,8 @@ int rxrpc_kernel_recv_data(struct socket *, struct rxrpc_call *,
struct iov_iter *, size_t *, bool, u32 *, u16 *); struct iov_iter *, size_t *, bool, u32 *, u16 *);
bool rxrpc_kernel_abort_call(struct socket *, struct rxrpc_call *, bool rxrpc_kernel_abort_call(struct socket *, struct rxrpc_call *,
u32, int, enum rxrpc_abort_reason); u32, int, enum rxrpc_abort_reason);
void rxrpc_kernel_end_call(struct socket *, struct rxrpc_call *); void rxrpc_kernel_shutdown_call(struct socket *sock, struct rxrpc_call *call);
void rxrpc_kernel_put_call(struct socket *sock, struct rxrpc_call *call);
void rxrpc_kernel_get_peer(struct socket *, struct rxrpc_call *, void rxrpc_kernel_get_peer(struct socket *, struct rxrpc_call *,
struct sockaddr_rxrpc *); struct sockaddr_rxrpc *);
bool rxrpc_kernel_get_srtt(struct socket *, struct rxrpc_call *, u32 *); bool rxrpc_kernel_get_srtt(struct socket *, struct rxrpc_call *, u32 *);

View File

@ -342,31 +342,44 @@ static void rxrpc_dummy_notify_rx(struct sock *sk, struct rxrpc_call *rxcall,
} }
/** /**
* rxrpc_kernel_end_call - Allow a kernel service to end a call it was using * rxrpc_kernel_shutdown_call - Allow a kernel service to shut down a call it was using
* @sock: The socket the call is on * @sock: The socket the call is on
* @call: The call to end * @call: The call to end
* *
* Allow a kernel service to end a call it was using. The call must be * Allow a kernel service to shut down a call it was using. The call must be
* complete before this is called (the call should be aborted if necessary). * complete before this is called (the call should be aborted if necessary).
*/ */
void rxrpc_kernel_end_call(struct socket *sock, struct rxrpc_call *call) void rxrpc_kernel_shutdown_call(struct socket *sock, struct rxrpc_call *call)
{ {
_enter("%d{%d}", call->debug_id, refcount_read(&call->ref)); _enter("%d{%d}", call->debug_id, refcount_read(&call->ref));
mutex_lock(&call->user_mutex); mutex_lock(&call->user_mutex);
rxrpc_release_call(rxrpc_sk(sock->sk), call); if (!test_bit(RXRPC_CALL_RELEASED, &call->flags)) {
rxrpc_release_call(rxrpc_sk(sock->sk), call);
/* Make sure we're not going to call back into a kernel service */ /* Make sure we're not going to call back into a kernel service */
if (call->notify_rx) { if (call->notify_rx) {
spin_lock(&call->notify_lock); spin_lock(&call->notify_lock);
call->notify_rx = rxrpc_dummy_notify_rx; call->notify_rx = rxrpc_dummy_notify_rx;
spin_unlock(&call->notify_lock); spin_unlock(&call->notify_lock);
}
} }
mutex_unlock(&call->user_mutex); mutex_unlock(&call->user_mutex);
}
EXPORT_SYMBOL(rxrpc_kernel_shutdown_call);
/**
* rxrpc_kernel_put_call - Release a reference to a call
* @sock: The socket the call is on
* @call: The call to put
*
* Drop the application's ref on an rxrpc call.
*/
void rxrpc_kernel_put_call(struct socket *sock, struct rxrpc_call *call)
{
rxrpc_put_call(call, rxrpc_call_put_kernel); rxrpc_put_call(call, rxrpc_call_put_kernel);
} }
EXPORT_SYMBOL(rxrpc_kernel_end_call); EXPORT_SYMBOL(rxrpc_kernel_put_call);
/** /**
* rxrpc_kernel_check_life - Check to see whether a call is still alive * rxrpc_kernel_check_life - Check to see whether a call is still alive

View File

@ -342,7 +342,8 @@ static void rxperf_deliver_to_call(struct work_struct *work)
call_complete: call_complete:
rxperf_set_call_complete(call, ret, remote_abort); rxperf_set_call_complete(call, ret, remote_abort);
/* The call may have been requeued */ /* The call may have been requeued */
rxrpc_kernel_end_call(rxperf_socket, call->rxcall); rxrpc_kernel_shutdown_call(rxperf_socket, call->rxcall);
rxrpc_kernel_put_call(rxperf_socket, call->rxcall);
cancel_work(&call->work); cancel_work(&call->work);
kfree(call); kfree(call);
} }