net/handshake: Use spin_lock_bh for hn_lock

nvmet_tcp_state_change(), a socket callback that runs in BH context,
can reach handshake_req_cancel() via nvmet_tcp_schedule_release_queue()
and tls_handshake_cancel().  handshake_req_cancel() acquires
hn->hn_lock with plain spin_lock().  If a process-context thread on
the same CPU holds hn->hn_lock when a softirq invokes the cancel path,
the lock attempt deadlocks.  This is the only caller that invokes
tls_handshake_cancel() from BH context; every other consumer calls it
from process context.

Deferring the cancel to process context in the NVMe target is not
straightforward: nvmet_tcp_schedule_release_queue() must call
tls_handshake_cancel() atomically with its state transition to
DISCONNECTING.  If the cancel were deferred, the handshake completion
callback could fire in the window before the cancel runs, observe the
unexpected state, and return without dropping its kref on the queue.
Reworking that interlock is considerably more invasive than hardening
the handshake lock.  Convert all hn->hn_lock acquisitions from
spin_lock/spin_unlock to spin_lock_bh/spin_unlock_bh so the lock is
never taken with softirqs enabled.

Fixes: 675b453e02 ("nvmet-tcp: enable TLS handshake upcall")
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Reviewed-by: Hannes Reinecke <hare@kernel.org>
Link: https://patch.msgid.link/20260525-handshake-file-pin-v3-1-66c616906ead@oracle.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
This commit is contained in:
Chuck Lever 2026-05-25 12:51:15 -04:00 committed by Paolo Abeni
parent 98d0912e9f
commit cc993e0927
3 changed files with 11 additions and 9 deletions

View File

@ -202,10 +202,10 @@ static void __net_exit handshake_net_exit(struct net *net)
* accepted and are in progress will be destroyed when
* the socket is closed.
*/
spin_lock(&hn->hn_lock);
spin_lock_bh(&hn->hn_lock);
set_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags);
list_splice_init(&requests, &hn->hn_requests);
spin_unlock(&hn->hn_lock);
spin_unlock_bh(&hn->hn_lock);
while (!list_empty(&requests)) {
req = list_first_entry(&requests, struct handshake_req, hr_list);

View File

@ -167,12 +167,12 @@ static bool remove_pending(struct handshake_net *hn, struct handshake_req *req)
{
bool ret = false;
spin_lock(&hn->hn_lock);
spin_lock_bh(&hn->hn_lock);
if (!list_empty(&req->hr_list)) {
__remove_pending_locked(hn, req);
ret = true;
}
spin_unlock(&hn->hn_lock);
spin_unlock_bh(&hn->hn_lock);
return ret;
}
@ -182,7 +182,7 @@ struct handshake_req *handshake_req_next(struct handshake_net *hn, int class)
struct handshake_req *req, *pos;
req = NULL;
spin_lock(&hn->hn_lock);
spin_lock_bh(&hn->hn_lock);
list_for_each_entry(pos, &hn->hn_requests, hr_list) {
if (pos->hr_proto->hp_handler_class != class)
continue;
@ -190,7 +190,7 @@ struct handshake_req *handshake_req_next(struct handshake_net *hn, int class)
req = pos;
break;
}
spin_unlock(&hn->hn_lock);
spin_unlock_bh(&hn->hn_lock);
return req;
}
@ -249,7 +249,7 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req,
if (READ_ONCE(hn->hn_pending) >= hn->hn_pending_max)
goto out_err;
spin_lock(&hn->hn_lock);
spin_lock_bh(&hn->hn_lock);
ret = -EOPNOTSUPP;
if (test_bit(HANDSHAKE_F_NET_DRAINING, &hn->hn_flags))
goto out_unlock;
@ -258,7 +258,7 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req,
goto out_unlock;
if (!__add_pending_locked(hn, req))
goto out_unlock;
spin_unlock(&hn->hn_lock);
spin_unlock_bh(&hn->hn_lock);
ret = handshake_genl_notify(net, req->hr_proto, flags);
if (ret) {
@ -274,7 +274,7 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req,
return 0;
out_unlock:
spin_unlock(&hn->hn_lock);
spin_unlock_bh(&hn->hn_lock);
out_err:
/* Restore original destructor so socket teardown still runs on failure */
req->hr_sk->sk_destruct = req->hr_odestruct;

View File

@ -425,6 +425,8 @@ EXPORT_SYMBOL(tls_server_hello_psk);
* Request cancellation races with request completion. To determine
* who won, callers examine the return value from this function.
*
* Context: May be called from process or softirq context.
*
* Return values:
* %true - Uncompleted handshake request was canceled
* %false - Handshake request already completed or not found