* [PATCH 6.18.y 1/5] file: add FD_{ADD,PREPARE}()
2026-08-05 9:13 [PATCH 6.18.y 0/5] Backport fixes for handshake Wentao Guan
@ 2026-08-05 9:13 ` Wentao Guan
2026-08-05 9:13 ` [PATCH 6.18.y 2/5] net/handshake: convert handshake_nl_accept_doit() to FD_PREPARE() Wentao Guan
` (4 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Wentao Guan @ 2026-08-05 9:13 UTC (permalink / raw)
To: sashal; +Cc: stable, gregkh, Christian Brauner, Wentao Guan
From: Christian Brauner <brauner@kernel.org>
I've been playing with this to allow for moderately flexible usage of
the get_unused_fd_flags() + create file + fd_install() pattern that's
used quite extensively.
How callers allocate files is really heterogenous so it's not really
convenient to fold them into a single class. It's possibe to split them
into subclasses like for anon inodes. I think that's not necessarily
nice as well.
My take is to add two primites:
(1) FD_ADD() the simple cases a file is installed:
fd = FD_ADD(O_CLOEXEC, open_file(some, args)));
if (fd >= 0)
kvm_get_kvm(vcpu->kvm);
return fd;
(2) FD_PREPARE() that captures all the cases where access to fd or file
or additional work before publishing the fd is needed:
FD_PREPARE(fdf, open_flag, file_open_handle(&path, open_flag));
if (fdf.err)
return fdf.err;
if (copy_to_user(/* something something */))
return -EFAULT;
return fd_publish(fdf);
I've converted all of the easy cases over to it and it gets rid of an
aweful lot of convoluted cleanup logic.
It's centered around struct fd_prepare. FD_PREPARE() encapsulates all of
allocation and cleanup logic and must be followed by a call to
fd_publish() which associates the fd with the file and installs it into
the callers fdtable. If fd_publish() isn't called both are deallocated.
It mandates a specific order namely that first we allocate the fd and
then instantiate the file. But that shouldn't be a problem nearly
everyone I've converted uses this exact pattern anyway.
There's a bunch of additional cases where it would be easy to convert
them to this pattern. For example, the whole sync file stuff in dma
currently retains the containing structure of the file instead of the
file itself even though it's only used to allocate files. Changing that
would make it fall into the FD_PREPARE() pattern easily. I've not done
that work yet.
There's room for extending this in a way that wed'd have subclasses for
some particularly often use patterns but as I said I'm not even sure
that's worth it.
Link: https://patch.msgid.link/20251123-work-fd-prepare-v4-1-b6efa1706cfd@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
(cherry picked from commit 011703a9acd76edc7c85d80dbccb6e50dba53aad)
Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
---
include/linux/cleanup.h | 7 +++
include/linux/file.h | 126 ++++++++++++++++++++++++++++++++++++++++
2 files changed, 133 insertions(+)
diff --git a/include/linux/cleanup.h b/include/linux/cleanup.h
index a1194e44b5276..bacefa0f1512f 100644
--- a/include/linux/cleanup.h
+++ b/include/linux/cleanup.h
@@ -261,6 +261,10 @@ const volatile void * __must_check_fn(const volatile void *val)
* CLASS(name, var)(args...):
* declare the variable @var as an instance of the named class
*
+ * CLASS_INIT(name, var, init_expr):
+ * declare the variable @var as an instance of the named class with
+ * custom initialization expression.
+ *
* Ex.
*
* DEFINE_CLASS(fdget, struct fd, fdput(_T), fdget(fd), int fd)
@@ -290,6 +294,9 @@ static inline class_##_name##_t class_##_name##ext##_constructor(_init_args) \
class_##_name##_t var __cleanup(class_##_name##_destructor) = \
class_##_name##_constructor
+#define CLASS_INIT(_name, _var, _init_expr) \
+ class_##_name##_t _var __cleanup(class_##_name##_destructor) = (_init_expr)
+
#define __scoped_class(_name, var, _label, args...) \
for (CLASS(_name, var)(args); ; ({ goto _label; })) \
if (0) { \
diff --git a/include/linux/file.h b/include/linux/file.h
index af1768d934a01..cf389fde9bc28 100644
--- a/include/linux/file.h
+++ b/include/linux/file.h
@@ -127,4 +127,130 @@ extern void __fput_sync(struct file *);
extern unsigned int sysctl_nr_open_min, sysctl_nr_open_max;
+/*
+ * fd_prepare: Combined fd + file allocation cleanup class.
+ * @err: Error code to indicate if allocation succeeded.
+ * @__fd: Allocated fd (may not be accessed directly)
+ * @__file: Allocated struct file pointer (may not be accessed directly)
+ *
+ * Allocates an fd and a file together. On error paths, automatically cleans
+ * up whichever resource was successfully allocated. Allows flexible file
+ * allocation with different functions per usage.
+ *
+ * Do not use directly.
+ */
+struct fd_prepare {
+ s32 err;
+ s32 __fd; /* do not access directly */
+ struct file *__file; /* do not access directly */
+};
+
+/* Typedef for fd_prepare cleanup guards. */
+typedef struct fd_prepare class_fd_prepare_t;
+
+/*
+ * Accessors for fd_prepare class members.
+ * _Generic() is used for zero-cost type safety.
+ */
+#define fd_prepare_fd(_fdf) \
+ (_Generic((_fdf), struct fd_prepare: (_fdf).__fd))
+
+#define fd_prepare_file(_fdf) \
+ (_Generic((_fdf), struct fd_prepare: (_fdf).__file))
+
+/* Do not use directly. */
+static inline void class_fd_prepare_destructor(const struct fd_prepare *fdf)
+{
+ if (unlikely(fdf->err)) {
+ if (likely(fdf->__fd >= 0))
+ put_unused_fd(fdf->__fd);
+ if (unlikely(!IS_ERR_OR_NULL(fdf->__file)))
+ fput(fdf->__file);
+ }
+}
+
+/* Do not use directly. */
+static inline int class_fd_prepare_lock_err(const struct fd_prepare *fdf)
+{
+ if (unlikely(fdf->err))
+ return fdf->err;
+ if (unlikely(fdf->__fd < 0))
+ return fdf->__fd;
+ if (unlikely(IS_ERR(fdf->__file)))
+ return PTR_ERR(fdf->__file);
+ if (unlikely(!fdf->__file))
+ return -ENOMEM;
+ return 0;
+}
+
+/*
+ * __FD_PREPARE_INIT - Helper to initialize fd_prepare class.
+ * @_fd_flags: flags for get_unused_fd_flags()
+ * @_file_owned: expression that returns struct file *
+ *
+ * Returns a struct fd_prepare with fd, file, and err set.
+ * If fd allocation fails, fd will be negative and err will be set. If
+ * fd succeeds but file_init_expr fails, file will be ERR_PTR and err
+ * will be set. The err field is the single source of truth for error
+ * checking.
+ */
+#define __FD_PREPARE_INIT(_fd_flags, _file_owned) \
+ ({ \
+ struct fd_prepare fdf = { \
+ .__fd = get_unused_fd_flags((_fd_flags)), \
+ }; \
+ if (likely(fdf.__fd >= 0)) \
+ fdf.__file = (_file_owned); \
+ fdf.err = ACQUIRE_ERR(fd_prepare, &fdf); \
+ fdf; \
+ })
+
+/*
+ * FD_PREPARE - Macro to declare and initialize an fd_prepare variable.
+ *
+ * Declares and initializes an fd_prepare variable with automatic
+ * cleanup. No separate scope required - cleanup happens when variable
+ * goes out of scope.
+ *
+ * @_fdf: name of struct fd_prepare variable to define
+ * @_fd_flags: flags for get_unused_fd_flags()
+ * @_file_owned: struct file to take ownership of (can be expression)
+ */
+#define FD_PREPARE(_fdf, _fd_flags, _file_owned) \
+ CLASS_INIT(fd_prepare, _fdf, __FD_PREPARE_INIT(_fd_flags, _file_owned))
+
+/*
+ * fd_publish - Publish prepared fd and file to the fd table.
+ * @_fdf: struct fd_prepare variable
+ */
+#define fd_publish(_fdf) \
+ ({ \
+ struct fd_prepare *fdp = &(_fdf); \
+ VFS_WARN_ON_ONCE(fdp->err); \
+ VFS_WARN_ON_ONCE(fdp->__fd < 0); \
+ VFS_WARN_ON_ONCE(IS_ERR_OR_NULL(fdp->__file)); \
+ fd_install(fdp->__fd, fdp->__file); \
+ fdp->__fd; \
+ })
+
+/* Do not use directly. */
+#define __FD_ADD(_fdf, _fd_flags, _file_owned) \
+ ({ \
+ FD_PREPARE(_fdf, _fd_flags, _file_owned); \
+ s32 ret = _fdf.err; \
+ if (likely(!ret)) \
+ ret = fd_publish(_fdf); \
+ ret; \
+ })
+
+/*
+ * FD_ADD - Allocate and install an fd and file in one step.
+ * @_fd_flags: flags for get_unused_fd_flags()
+ * @_file_owned: struct file to take ownership of
+ *
+ * Returns the allocated fd number, or negative error code on failure.
+ */
+#define FD_ADD(_fd_flags, _file_owned) \
+ __FD_ADD(__UNIQUE_ID(fd_prepare), _fd_flags, _file_owned)
+
#endif /* __LINUX_FILE_H */
--
2.30.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.18.y 2/5] net/handshake: convert handshake_nl_accept_doit() to FD_PREPARE()
2026-08-05 9:13 [PATCH 6.18.y 0/5] Backport fixes for handshake Wentao Guan
2026-08-05 9:13 ` [PATCH 6.18.y 1/5] file: add FD_{ADD,PREPARE}() Wentao Guan
@ 2026-08-05 9:13 ` Wentao Guan
2026-08-05 9:13 ` [PATCH 6.18.y 3/5] net/handshake: Take a long-lived file reference at submit Wentao Guan
` (3 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Wentao Guan @ 2026-08-05 9:13 UTC (permalink / raw)
To: sashal; +Cc: stable, gregkh, Christian Brauner, Wentao Guan
From: Christian Brauner <brauner@kernel.org>
Link: https://patch.msgid.link/20251123-work-fd-prepare-v4-27-b6efa1706cfd@kernel.org
Signed-off-by: Christian Brauner <brauner@kernel.org>
(cherry picked from commit fe67b063f687d8f32c466778aad57508bc1c618a)
Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
---
net/handshake/netlink.c | 38 ++++++++++++++++++--------------------
1 file changed, 18 insertions(+), 20 deletions(-)
diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c
index d8211e0ba75c6..7b1d42607ae68 100644
--- a/net/handshake/netlink.c
+++ b/net/handshake/netlink.c
@@ -93,7 +93,7 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info)
struct handshake_net *hn = handshake_pernet(net);
struct handshake_req *req = NULL;
struct socket *sock;
- int class, fd, err;
+ int class, err;
err = -EOPNOTSUPP;
if (!hn)
@@ -106,27 +106,25 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info)
err = -EAGAIN;
req = handshake_req_next(hn, class);
- if (!req)
- goto out_status;
-
- sock = req->hr_sk->sk_socket;
- fd = get_unused_fd_flags(O_CLOEXEC);
- if (fd < 0) {
- err = fd;
- goto out_complete;
- }
-
- err = req->hr_proto->hp_accept(req, info, fd);
- if (err) {
- put_unused_fd(fd);
- goto out_complete;
+ if (req) {
+ sock = req->hr_sk->sk_socket;
+
+ FD_PREPARE(fdf, O_CLOEXEC, sock->file);
+ if (fdf.err) {
+ err = fdf.err;
+ goto out_complete;
+ }
+
+ get_file(sock->file); /* FD_PREPARE() consumes a reference. */
+ err = req->hr_proto->hp_accept(req, info, fd_prepare_fd(fdf));
+ if (err)
+ goto out_complete; /* Automatic cleanup handles fput */
+
+ trace_handshake_cmd_accept(net, req, req->hr_sk, fd_prepare_fd(fdf));
+ fd_publish(fdf);
+ return 0;
}
- fd_install(fd, get_file(sock->file));
-
- trace_handshake_cmd_accept(net, req, req->hr_sk, fd);
- return 0;
-
out_complete:
handshake_complete(req, -EIO, NULL);
out_status:
--
2.30.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.18.y 3/5] net/handshake: Take a long-lived file reference at submit
2026-08-05 9:13 [PATCH 6.18.y 0/5] Backport fixes for handshake Wentao Guan
2026-08-05 9:13 ` [PATCH 6.18.y 1/5] file: add FD_{ADD,PREPARE}() Wentao Guan
2026-08-05 9:13 ` [PATCH 6.18.y 2/5] net/handshake: convert handshake_nl_accept_doit() to FD_PREPARE() Wentao Guan
@ 2026-08-05 9:13 ` Wentao Guan
2026-08-05 9:13 ` [PATCH 6.18.y 4/5] net/handshake: hand off the pinned file reference to accept_doit Wentao Guan
` (2 subsequent siblings)
5 siblings, 0 replies; 8+ messages in thread
From: Wentao Guan @ 2026-08-05 9:13 UTC (permalink / raw)
To: sashal; +Cc: stable, gregkh, Chuck Lever, Paolo Abeni, Wentao Guan
From: Chuck Lever <chuck.lever@oracle.com>
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.
Add an hr_file pointer to struct handshake_req and acquire an
explicit reference on sock->file during handshake_req_submit().
handshake_complete() and handshake_req_cancel() release the
reference on the completion-bit-winning path.
The submit error path must also release the file reference, but
after rhashtable insertion a concurrent handshake_req_cancel() can
discover the request and race the error path. Gate the error-path
cleanup -- sk_destruct restoration, fput, and request destruction
-- with test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED), the same
serialization handshake_complete() and handshake_req_cancel()
already use. When cancel has already claimed ownership, the submit
error path returns without touching the request; socket teardown
handles final destruction.
The accept-side dereferences are not yet retargeted; that change
comes in the next patch.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Link: https://patch.msgid.link/20260525-handshake-file-pin-v3-4-66c616906ead@oracle.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
(cherry picked from commit 09dba37eee70d0596e26645015f1aa95a9848e9d)
Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
---
net/handshake/handshake.h | 2 ++
net/handshake/netlink.c | 6 ------
net/handshake/request.c | 42 ++++++++++++++++++++++++++++++++-------
3 files changed, 37 insertions(+), 13 deletions(-)
diff --git a/net/handshake/handshake.h b/net/handshake/handshake.h
index 2289b0e274f40..da61cadd1ad3e 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 7b1d42607ae68..fe4e22b87b3eb 100644
--- a/net/handshake/netlink.c
+++ b/net/handshake/netlink.c
@@ -209,12 +209,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(&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 62efb7e32730e..35bc6290e1203 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>
@@ -215,9 +216,16 @@ EXPORT_SYMBOL_IF_KUNIT(handshake_req_next);
* A zero return value from handshake_req_submit() means that
* exactly one subsequent completion callback is guaranteed.
*
- * A negative return value from handshake_req_submit() means that
- * no completion callback will be done and that @req has been
- * destroyed.
+ * A negative return value from handshake_req_submit() guarantees that
+ * no completion callback will occur and that @req is no longer owned by
+ * the caller. If cancellation wins the completion race after the request
+ * has been published, final destruction is deferred until socket teardown.
+ *
+ * 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)
@@ -236,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;
@@ -267,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);
@@ -276,10 +296,13 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req,
out_unlock:
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;
trace_handshake_submit_err(net, req, req->hr_sk, ret);
- handshake_req_destroy(req);
+ if (!test_and_set_bit(HANDSHAKE_F_REQ_COMPLETED, &req->hr_flags)) {
+ /* Restore original destructor so socket teardown still runs. */
+ req->hr_sk->sk_destruct = req->hr_odestruct;
+ fput(req->hr_file);
+ handshake_req_destroy(req);
+ }
return ret;
}
EXPORT_SYMBOL(handshake_req_submit);
@@ -291,11 +314,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);
@@ -344,6 +371,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.30.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.18.y 4/5] net/handshake: hand off the pinned file reference to accept_doit
2026-08-05 9:13 [PATCH 6.18.y 0/5] Backport fixes for handshake Wentao Guan
` (2 preceding siblings ...)
2026-08-05 9:13 ` [PATCH 6.18.y 3/5] net/handshake: Take a long-lived file reference at submit Wentao Guan
@ 2026-08-05 9:13 ` Wentao Guan
2026-08-05 9:13 ` [PATCH 6.18.y 5/5] net/handshake: Close the submit-side sock_hold race Wentao Guan
2026-08-05 16:41 ` [PATCH 6.18.y 0/5] Backport fixes for handshake Sasha Levin
5 siblings, 0 replies; 8+ messages in thread
From: Wentao Guan @ 2026-08-05 9:13 UTC (permalink / raw)
To: sashal
Cc: stable, gregkh, Chuck Lever, Chris Mason, Hannes Reinecke,
Paolo Abeni, Wentao Guan
From: Chuck Lever <chuck.lever@oracle.com>
handshake_req_next() removes the request from the per-net
pending list and drops hn_lock before handshake_nl_accept_doit()
reads req->hr_sk->sk_socket and dereferences sock->file (once in
FD_PREPARE() and again in get_file()). In that window a
consumer running tls_handshake_cancel() followed by sockfd_put()
(svc_sock_free) or __fput_sync() (xs_reset_transport) releases
sock->file. sock_release() then runs sock_orphan(), zeroing
sk_socket, and frees the struct socket. The accept-side code
either reads NULL through sk_socket or chases freed memory.
The submit-side sock_hold() does not prevent this. sk_refcnt
protects struct sock, but struct socket and sock->file are
independently refcounted via the file descriptor the consumer
owns. Pinning sk leaves sock and sock->file unprotected.
Retarget the accept-side dereferences at req->hr_file, which was
pinned at submit time, instead of req->hr_sk->sk_socket->file.
Pinning on its own is not sufficient: a consumer that cancels
between handshake_req_next() returning and accept_doit reaching
FD_PREPARE() takes the !remove_pending() branch in
handshake_req_cancel() and drops hr_file before the accept side
takes its own reference. Hand off an additional file reference
inside handshake_req_next(), under hn_lock, so the accept side
operates on a reference that no concurrent handshake_req_cancel()
can revoke. FD_PREPARE() consumes that handed-off reference,
either by transferring it to the new fd in fd_publish() or by
dropping it in the cleanup destructor on error; the explicit
get_file() that previously balanced FD_PREPARE() is therefore
redundant and goes away.
Update handshake_req_cancel_test2 and _test3 to simulate the
FD_PREPARE() consumption with an fput() so the kunit file-count
assertions stay balanced.
Reported-by: Chris Mason <clm@meta.com>
Fixes: 3b3009ea8abb ("net/handshake: Create a NETLINK service for handling handshake requests")
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-5-66c616906ead@oracle.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
(cherry picked from commit f4251190e58b209999c1ba9e6d2976136a1be055)
Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
---
net/handshake/handshake-test.c | 8 ++++++++
net/handshake/netlink.c | 7 ++-----
net/handshake/request.c | 18 ++++++++++++++++++
3 files changed, 28 insertions(+), 5 deletions(-)
diff --git a/net/handshake/handshake-test.c b/net/handshake/handshake-test.c
index df3948e807a0f..9cc7a95f41207 100644
--- a/net/handshake/handshake-test.c
+++ b/net/handshake/handshake-test.c
@@ -375,6 +375,10 @@ static void handshake_req_cancel_test2(struct kunit *test)
/* Pretend to accept this request */
next = handshake_req_next(hn, HANDSHAKE_HANDLER_CLASS_TLSHD);
KUNIT_ASSERT_PTR_EQ(test, req, next);
+ /* Simulate FD_PREPARE() consuming the file reference handed
+ * off by handshake_req_next(); see handshake_nl_accept_doit().
+ */
+ fput(filp);
/* Act */
result = handshake_req_cancel(sock->sk);
@@ -417,6 +421,10 @@ static void handshake_req_cancel_test3(struct kunit *test)
/* Pretend to accept this request */
next = handshake_req_next(hn, HANDSHAKE_HANDLER_CLASS_TLSHD);
KUNIT_ASSERT_PTR_EQ(test, req, next);
+ /* Simulate FD_PREPARE() consuming the file reference handed
+ * off by handshake_req_next(); see handshake_nl_accept_doit().
+ */
+ fput(filp);
/* Pretend to complete this request */
handshake_complete(next, -ETIMEDOUT, NULL);
diff --git a/net/handshake/netlink.c b/net/handshake/netlink.c
index fe4e22b87b3eb..67a78db7978aa 100644
--- a/net/handshake/netlink.c
+++ b/net/handshake/netlink.c
@@ -92,7 +92,6 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info)
struct net *net = sock_net(skb->sk);
struct handshake_net *hn = handshake_pernet(net);
struct handshake_req *req = NULL;
- struct socket *sock;
int class, err;
err = -EOPNOTSUPP;
@@ -107,15 +106,13 @@ int handshake_nl_accept_doit(struct sk_buff *skb, struct genl_info *info)
err = -EAGAIN;
req = handshake_req_next(hn, class);
if (req) {
- sock = req->hr_sk->sk_socket;
-
- FD_PREPARE(fdf, O_CLOEXEC, sock->file);
+ FD_PREPARE(fdf, O_CLOEXEC, req->hr_file);
if (fdf.err) {
+ fput(req->hr_file); /* drop ref from handshake_req_next() */
err = fdf.err;
goto out_complete;
}
- get_file(sock->file); /* FD_PREPARE() consumes a reference. */
err = req->hr_proto->hp_accept(req, info, fd_prepare_fd(fdf));
if (err)
goto out_complete; /* Automatic cleanup handles fput */
diff --git a/net/handshake/request.c b/net/handshake/request.c
index 35bc6290e1203..4bf98793491aa 100644
--- a/net/handshake/request.c
+++ b/net/handshake/request.c
@@ -178,6 +178,17 @@ static bool remove_pending(struct handshake_net *hn, struct handshake_req *req)
return ret;
}
+/**
+ * handshake_req_next - Return the next queued handshake request
+ * @hn: per-net handshake state
+ * @class: handler class to match
+ *
+ * On a non-NULL return, the caller owns an extra reference
+ * on @req->hr_file. FD_PREPARE() consumes it on success; on
+ * the FD_PREPARE() failure path the caller must fput() it.
+ *
+ * Return: pointer to a removed handshake_req, or NULL.
+ */
struct handshake_req *handshake_req_next(struct handshake_net *hn, int class)
{
struct handshake_req *req, *pos;
@@ -188,6 +199,13 @@ struct handshake_req *handshake_req_next(struct handshake_net *hn, int class)
if (pos->hr_proto->hp_handler_class != class)
continue;
__remove_pending_locked(hn, pos);
+ /* Hand off a file reference to the accept side under
+ * hn_lock. A concurrent handshake_req_cancel() can drop
+ * hr_file before accept reaches FD_PREPARE(); this extra
+ * reference keeps the file alive until FD_PREPARE() takes
+ * ownership.
+ */
+ get_file(pos->hr_file);
req = pos;
break;
}
--
2.30.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* [PATCH 6.18.y 5/5] net/handshake: Close the submit-side sock_hold race
2026-08-05 9:13 [PATCH 6.18.y 0/5] Backport fixes for handshake Wentao Guan
` (3 preceding siblings ...)
2026-08-05 9:13 ` [PATCH 6.18.y 4/5] net/handshake: hand off the pinned file reference to accept_doit Wentao Guan
@ 2026-08-05 9:13 ` Wentao Guan
2026-08-05 16:41 ` [PATCH 6.18.y 0/5] Backport fixes for handshake Sasha Levin
5 siblings, 0 replies; 8+ messages in thread
From: Wentao Guan @ 2026-08-05 9:13 UTC (permalink / raw)
To: sashal
Cc: stable, gregkh, Chuck Lever, Hannes Reinecke, Paolo Abeni,
Wentao Guan
From: Chuck Lever <chuck.lever@oracle.com>
handshake_req_submit() publishes the request via
handshake_req_hash_add() and __add_pending_locked(), drops
hn_lock, and calls handshake_genl_notify() (which can sleep)
before taking sock_hold() on req->hr_sk. A fast tlshd ACCEPT
followed by DONE can drive handshake_complete()'s sock_put()
into the window between the spin_unlock and the late
sock_hold(); on a system where the consumer's fd held the
only sk reference, the late sock_hold() then operates on an
sk whose refcount has reached zero.
The preceding two patches install an explicit file reference
on struct handshake_req. That file pins sock->file, which
pins the embedded struct socket, which defers inet_release()'s
sock_put(). As long as hr_file is held, sk cannot reach refcount
zero from the consumer side, and the submit-side sock_hold()
with its matching sock_put() calls in handshake_complete() and
handshake_req_cancel() is now redundant.
Drop all three. The file reference already keeps each request's
socket alive, and the lifetime story is contained in a single
get_file()/fput() pair.
Fixes: 3b3009ea8abb ("net/handshake: Create a NETLINK service for handling handshake requests")
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-6-66c616906ead@oracle.com
Signed-off-by: Paolo Abeni <pabeni@redhat.com>
(cherry picked from commit 5da98f55b13173c08f003011b76531b25c821c07)
Signed-off-by: Wentao Guan <guanwentao@uniontech.com>
---
net/handshake/request.c | 12 ------------
1 file changed, 12 deletions(-)
diff --git a/net/handshake/request.c b/net/handshake/request.c
index 4bf98793491aa..035bb2d7471c9 100644
--- a/net/handshake/request.c
+++ b/net/handshake/request.c
@@ -301,13 +301,6 @@ int handshake_req_submit(struct socket *sock, struct handshake_req *req,
goto out_err;
}
- /*
- * 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);
return 0;
@@ -337,9 +330,6 @@ void handshake_complete(struct handshake_req *req, int status,
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);
}
}
@@ -387,8 +377,6 @@ bool handshake_req_cancel(struct sock *sk)
out_true:
trace_handshake_cancel(net, req, sk);
- /* Handshake request is no longer pending */
- sock_put(sk);
fput(req->hr_file);
return true;
}
--
2.30.2
^ permalink raw reply related [flat|nested] 8+ messages in thread* Re: [PATCH 6.18.y 0/5] Backport fixes for handshake
2026-08-05 9:13 [PATCH 6.18.y 0/5] Backport fixes for handshake Wentao Guan
` (4 preceding siblings ...)
2026-08-05 9:13 ` [PATCH 6.18.y 5/5] net/handshake: Close the submit-side sock_hold race Wentao Guan
@ 2026-08-05 16:41 ` Sasha Levin
2026-08-05 16:54 ` Wentao Guan
5 siblings, 1 reply; 8+ messages in thread
From: Sasha Levin @ 2026-08-05 16:41 UTC (permalink / raw)
To: Wentao Guan; +Cc: Sasha Levin, stable, gregkh
On Wed, Aug 05, 2026 at 05:13:44PM +0800, Wentao Guan wrote:
> Christian Brauner (2):
> file: add FD_{ADD,PREPARE}()
> net/handshake: convert handshake_nl_accept_doit() to FD_PREPARE()
>
> Chuck Lever (3):
> net/handshake: Take a long-lived file reference at submit
> net/handshake: hand off the pinned file reference to accept_doit
> net/handshake: Close the submit-side sock_hold race
The series is missing two follow-ups, and without them 6.18 ends up worse
off than it is today:
- dabac51b8102 ("net/handshake: Fix null-ptr-deref in handshake_complete()")
fixes fe67b063f687 ("net/handshake: convert handshake_nl_accept_doit() to
FD_PREPARE()"), your 2/5. After that conversion the ordinary "no pending
request" ACCEPT path falls through to handshake_complete(NULL, -EIO, NULL)
and dereferences req->hr_sk.
- d2ea4d254d04 ("file: ensure cleanup") fixes 011703a9acd7 ("file: add
FD_{ADD,PREPARE}()"), your 1/5. Without it the imported cleanup guard is
err-gated and no-ops on the accept-failure path, leaking an fd and a
struct file.
Either way, please get an ack from the handshake maintainers.
--
Thanks,
Sasha
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH 6.18.y 0/5] Backport fixes for handshake
2026-08-05 16:41 ` [PATCH 6.18.y 0/5] Backport fixes for handshake Sasha Levin
@ 2026-08-05 16:54 ` Wentao Guan
0 siblings, 0 replies; 8+ messages in thread
From: Wentao Guan @ 2026-08-05 16:54 UTC (permalink / raw)
To: Sasha Levin; +Cc: Sasha Levin, stable, Greg KH
Hi,
> On Wed, Aug 05, 2026 at 05:13:44PM +0800, Wentao Guan wrote:
> > Christian Brauner (2):
> > file: add FD_{ADD,PREPARE}()
> > net/handshake: convert handshake_nl_accept_doit() to FD_PREPARE()
> >
> > Chuck Lever (3):
> > net/handshake: Take a long-lived file reference at submit
> > net/handshake: hand off the pinned file reference to accept_doit
> > net/handshake: Close the submit-side sock_hold race
>
> The series is missing two follow-ups, and without them 6.18 ends up worse
> off than it is today:
>
> - dabac51b8102 ("net/handshake: Fix null-ptr-deref in handshake_complete()")
> fixes fe67b063f687 ("net/handshake: convert handshake_nl_accept_doit() to
> FD_PREPARE()"), your 2/5. After that conversion the ordinary "no pending
> request" ACCEPT path falls through to handshake_complete(NULL, -EIO, NULL)
> and dereferences req->hr_sk.
>
> - d2ea4d254d04 ("file: ensure cleanup") fixes 011703a9acd7 ("file: add
> FD_{ADD,PREPARE}()"), your 1/5. Without it the imported cleanup guard is
> err-gated and no-ops on the accept-failure path, leaking an fd and a
> struct file.
> Either way, please get an ack from the handshake maintainers.
Thanks for your review, forgeting double check the backport fixes,
I will fix them and send v2.
BRs
Wentao Guan
^ permalink raw reply [flat|nested] 8+ messages in thread