From: Chuck Lever 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 Reviewed-by: Hannes Reinecke --- 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 #include #include +#include #include #include @@ -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