[PATCH net v2 4/7] net/handshake: Take a long-lived file reference at submit
From: Chuck Lever <cel@kernel.org>
Date: 2026-05-21 14:47:36
Also in:
linux-nvme
Subsystem:
handshake upcall for transport layer security, networking [general], the rest · Maintainers:
Chuck Lever, "David S. Miller", Eric Dumazet, Jakub Kicinski, Paolo Abeni, Linus Torvalds
From: Chuck Lever <redacted> handshake_nl_accept_doit() needs the file pointer backing req->hr_sk->sk_socket to survive the window between handshake_req_next() and the subsequent FD_PREPARE() and get_file(). The submit-side sock_hold() does not provide that. sk_refcnt keeps struct sock alive, but struct socket is owned by sock->file: when the consumer fputs the last file reference, sock_release() tears the socket down regardless of any sock_hold. Prepare for a fix by adding an hr_file pointer to struct handshake_req and acquiring an explicit reference on sock->file during handshake_req_submit(). Release the reference on the completion-bit-winning path of handshake_complete() and handshake_req_cancel(), and on the submit error paths that destroy the request before it has been published. The accept-side dereferences are not yet retargeted; that change comes in the next patch. Signed-off-by: Chuck Lever <redacted> Reviewed-by: Hannes Reinecke <hare@kernel.org> --- net/handshake/handshake.h | 2 ++ net/handshake/netlink.c | 6 ------ net/handshake/request.c | 27 ++++++++++++++++++++++++++- 3 files changed, 28 insertions(+), 7 deletions(-)
diff --git a/net/handshake/handshake.h b/net/handshake/handshake.h
index 2289b0e274f4..da61cadd1ad3 100644
--- a/net/handshake/handshake.h
+++ b/net/handshake/handshake.h@@ -24,6 +24,7 @@ enum hn_flags_bits { HANDSHAKE_F_NET_DRAINING, }; +struct file; struct handshake_proto; /* One handshake request */
@@ -32,6 +33,7 @@ struct handshake_req { struct rhash_head hr_rhash; unsigned long hr_flags; const struct handshake_proto *hr_proto; + struct file *hr_file; struct sock *hr_sk; void (*hr_odestruct)(struct sock *sk);
diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c
index 18e1268efc7e..f395bfc7ff8c 100644
--- a/net/handshake/netlink.c
+++ b/net/handshake/netlink.c@@ -210,12 +210,6 @@ static void __net_exit handshake_net_exit(struct net *net) while (!list_empty(&requests)) { req = list_first_entry(&requests, struct handshake_req, hr_list); list_del_init(&req->hr_list); - - /* - * Requests on this list have not yet been - * accepted, so they do not have an fd to put. - */ - handshake_complete(req, -ETIMEDOUT, NULL); } }
diff --git a/net/handshake/request.c b/net/handshake/request.c
index 93cec5f0e16e..b076ae539422 100644
--- a/net/handshake/request.c
+++ b/net/handshake/request.c@@ -13,6 +13,7 @@ #include <linux/module.h> #include <linux/skbuff.h> #include <linux/inet.h> +#include <linux/file.h> #include <linux/rhashtable.h> #include <net/sock.h>
@@ -219,6 +220,12 @@ EXPORT_SYMBOL_IF_KUNIT(handshake_req_next); * A negative return value from handshake_req_submit() means that * no completion callback will be done and that @req has been * destroyed. + * + * The caller must hold a reference on @sock->file for the duration + * of this call. Once the request is published to the accept side, a + * concurrent completion or cancellation may release the request's pin on + * @sock->file; the caller's reference is what keeps @sock->sk valid until + * handshake_req_submit() returns. */ int handshake_req_submit(struct socket *sock, struct handshake_req *req, gfp_t flags)
@@ -237,6 +244,14 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, kfree(req); return -EINVAL; } + + /* + * Pin sock->file for the lifetime of the request so the + * accept side does not race a consumer that releases the + * socket while a handshake is pending. + */ + req->hr_file = get_file(sock->file); + req->hr_odestruct = req->hr_sk->sk_destruct; req->hr_sk->sk_destruct = handshake_sk_destruct;
@@ -268,7 +283,11 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, goto out_err; } - /* Prevent socket release while a handshake request is pending */ + /* + * Pin struct sock so sk_destruct does not run until the + * handshake completion path releases it; struct socket is + * held separately via hr_file above. + */ sock_hold(req->hr_sk); trace_handshake_submit(net, req, req->hr_sk);
@@ -280,6 +299,7 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req, /* Restore original destructor so socket teardown still runs on failure */ req->hr_sk->sk_destruct = req->hr_odestruct; trace_handshake_submit_err(net, req, req->hr_sk, ret); + fput(req->hr_file); handshake_req_destroy(req); return ret; }
@@ -292,11 +312,15 @@ void handshake_complete(struct handshake_req *req, int status, struct net *net = sock_net(sk); if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) { + struct file *file = req->hr_file; + trace_handshake_complete(net, req, sk, status); req->hr_proto->hp_done(req, status, info); /* Handshake request is no longer pending */ sock_put(sk); + + fput(file); } } EXPORT_SYMBOL_IF_KUNIT(handshake_complete);
@@ -345,6 +369,7 @@ bool handshake_req_cancel(struct sock *sk) /* Handshake request is no longer pending */ sock_put(sk); + fput(req->hr_file); return true; } EXPORT_SYMBOL(handshake_req_cancel);
--
2.54.0