Skip to content

Commit 6555009

Browse files
dhowellsdavem330
authored andcommitted
rxrpc: Fix race between recvmsg and sendmsg on immediate call failure
There's a race between rxrpc_sendmsg setting up a call, but then failing to send anything on it due to an error, and recvmsg() seeing the call completion occur and trying to return the state to the user. An assertion fails in rxrpc_recvmsg() because the call has already been released from the socket and is about to be released again as recvmsg deals with it. (The recvmsg_q queue on the socket holds a ref, so there's no problem with use-after-free.) We also have to be careful not to end up reporting an error twice, in such a way that both returns indicate to userspace that the user ID supplied with the call is no longer in use - which could cause the client to malfunction if it recycles the user ID fast enough. Fix this by the following means: (1) When sendmsg() creates a call after the point that the call has been successfully added to the socket, don't return any errors through sendmsg(), but rather complete the call and let recvmsg() retrieve them. Make sendmsg() return 0 at this point. Further calls to sendmsg() for that call will fail with ESHUTDOWN. Note that at this point, we haven't send any packets yet, so the server doesn't yet know about the call. (2) If sendmsg() returns an error when it was expected to create a new call, it means that the user ID wasn't used. (3) Mark the call disconnected before marking it completed to prevent an oops in rxrpc_release_call(). (4) recvmsg() will then retrieve the error and set MSG_EOR to indicate that the user ID is no longer known by the kernel. An oops like the following is produced: kernel BUG at net/rxrpc/recvmsg.c:605! ... RIP: 0010:rxrpc_recvmsg+0x256/0x5ae ... Call Trace: ? __init_waitqueue_head+0x2f/0x2f ____sys_recvmsg+0x8a/0x148 ? import_iovec+0x69/0x9c ? copy_msghdr_from_user+0x5c/0x86 ___sys_recvmsg+0x72/0xaa ? __fget_files+0x22/0x57 ? __fget_light+0x46/0x51 ? fdget+0x9/0x1b do_recvmmsg+0x15e/0x232 ? _raw_spin_unlock+0xa/0xb ? vtime_delta+0xf/0x25 __x64_sys_recvmmsg+0x2c/0x2f do_syscall_64+0x4c/0x78 entry_SYSCALL_64_after_hwframe+0x44/0xa9 Fixes: 357f5ef ("rxrpc: Call rxrpc_release_call() on error in rxrpc_new_client_call()") Reported-by: [email protected] Signed-off-by: David Howells <[email protected]> Reviewed-by: Marc Dionne <[email protected]> Signed-off-by: David S. Miller <[email protected]>
1 parent 591eee6 commit 6555009

File tree

4 files changed

+28
-12
lines changed

4 files changed

+28
-12
lines changed

net/rxrpc/call_object.c

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -288,7 +288,7 @@ struct rxrpc_call *rxrpc_new_client_call(struct rxrpc_sock *rx,
288288
*/
289289
ret = rxrpc_connect_call(rx, call, cp, srx, gfp);
290290
if (ret < 0)
291-
goto error;
291+
goto error_attached_to_socket;
292292

293293
trace_rxrpc_call(call->debug_id, rxrpc_call_connected,
294294
atomic_read(&call->usage), here, NULL);
@@ -308,18 +308,29 @@ struct rxrpc_call *rxrpc_new_client_call(struct rxrpc_sock *rx,
308308
error_dup_user_ID:
309309
write_unlock(&rx->call_lock);
310310
release_sock(&rx->sk);
311-
ret = -EEXIST;
312-
313-
error:
314311
__rxrpc_set_call_completion(call, RXRPC_CALL_LOCAL_ERROR,
315-
RX_CALL_DEAD, ret);
312+
RX_CALL_DEAD, -EEXIST);
316313
trace_rxrpc_call(call->debug_id, rxrpc_call_error,
317-
atomic_read(&call->usage), here, ERR_PTR(ret));
314+
atomic_read(&call->usage), here, ERR_PTR(-EEXIST));
318315
rxrpc_release_call(rx, call);
319316
mutex_unlock(&call->user_mutex);
320317
rxrpc_put_call(call, rxrpc_call_put);
321-
_leave(" = %d", ret);
322-
return ERR_PTR(ret);
318+
_leave(" = -EEXIST");
319+
return ERR_PTR(-EEXIST);
320+
321+
/* We got an error, but the call is attached to the socket and is in
322+
* need of release. However, we might now race with recvmsg() when
323+
* completing the call queues it. Return 0 from sys_sendmsg() and
324+
* leave the error to recvmsg() to deal with.
325+
*/
326+
error_attached_to_socket:
327+
trace_rxrpc_call(call->debug_id, rxrpc_call_error,
328+
atomic_read(&call->usage), here, ERR_PTR(ret));
329+
set_bit(RXRPC_CALL_DISCONNECTED, &call->flags);
330+
__rxrpc_set_call_completion(call, RXRPC_CALL_LOCAL_ERROR,
331+
RX_CALL_DEAD, ret);
332+
_leave(" = c=%08x [err]", call->debug_id);
333+
return call;
323334
}
324335

325336
/*

net/rxrpc/conn_object.c

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,9 +212,11 @@ void rxrpc_disconnect_call(struct rxrpc_call *call)
212212

213213
call->peer->cong_cwnd = call->cong_cwnd;
214214

215-
spin_lock_bh(&conn->params.peer->lock);
216-
hlist_del_rcu(&call->error_link);
217-
spin_unlock_bh(&conn->params.peer->lock);
215+
if (!hlist_unhashed(&call->error_link)) {
216+
spin_lock_bh(&call->peer->lock);
217+
hlist_del_rcu(&call->error_link);
218+
spin_unlock_bh(&call->peer->lock);
219+
}
218220

219221
if (rxrpc_is_client_call(call))
220222
return rxrpc_disconnect_client_call(call);

net/rxrpc/recvmsg.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -620,7 +620,7 @@ int rxrpc_recvmsg(struct socket *sock, struct msghdr *msg, size_t len,
620620
goto error_unlock_call;
621621
}
622622

623-
if (msg->msg_name) {
623+
if (msg->msg_name && call->peer) {
624624
struct sockaddr_rxrpc *srx = msg->msg_name;
625625
size_t len = sizeof(call->peer->srx);
626626

net/rxrpc/sendmsg.c

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -681,6 +681,9 @@ int rxrpc_do_sendmsg(struct rxrpc_sock *rx, struct msghdr *msg, size_t len)
681681
if (IS_ERR(call))
682682
return PTR_ERR(call);
683683
/* ... and we have the call lock. */
684+
ret = 0;
685+
if (READ_ONCE(call->state) == RXRPC_CALL_COMPLETE)
686+
goto out_put_unlock;
684687
} else {
685688
switch (READ_ONCE(call->state)) {
686689
case RXRPC_CALL_UNINITIALISED:

0 commit comments

Comments
 (0)