* [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit
2026-07-17 6:27 [PATCH net-next v2 0/3] af_unix: Fix priority inversion issue Nam Cao
@ 2026-07-17 6:27 ` Nam Cao
2026-07-17 6:45 ` Nam Cao
2026-07-18 6:27 ` sashiko-bot
2026-07-17 6:27 ` [PATCH net-next v2 2/3] af_unix: Do not wait for garbage collector in sendmsg() Nam Cao
2026-07-17 6:27 ` [PATCH net-next v2 3/3] af_unix: Clean up unix_schedule_gc() Nam Cao
2 siblings, 2 replies; 8+ messages in thread
From: Nam Cao @ 2026-07-17 6:27 UTC (permalink / raw)
To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel,
linux-rt-devel
Cc: Nam Cao
When a task exits while still having dead cyclic reference AF_UNIX sockets,
those sockets stay behind indefinitely until the garbage collector gets
scheduled by an unrelated reason. This can be observed with the program
below.
Resolve this issue by scheduling the garbage collector during task exit,
after the task's file descriptors have been closed.
static int send_fd(int unix_fd, int fd)
{
struct msghdr msgh;
struct cmsghdr *cmsg;
char buf[CMSG_SPACE(sizeof(fd))];
memset(&msgh, 0, sizeof(msgh));
memset(buf, 0, sizeof(buf));
msgh.msg_control = buf;
msgh.msg_controllen = sizeof(buf);
cmsg = CMSG_FIRSTHDR(&msgh);
cmsg->cmsg_len = CMSG_LEN(sizeof(fd));
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS;
msgh.msg_controllen = cmsg->cmsg_len;
memcpy(CMSG_DATA(cmsg), &fd, sizeof(fd));
return sendmsg(unix_fd, &msgh, 0);
}
int main(int argc, char *argv[])
{
int fd[2];
int i;
for (int n = 0; n < 100; ++n) {
if (socketpair(PF_UNIX, SOCK_SEQPACKET, 0, fd) == -1)
goto out_error;
for (i = 0; i < 100; ++i) {
if (send_fd(fd[0], fd[0]) == -1)
goto out_error;
if (send_fd(fd[1], fd[1]) == -1)
goto out_error;
}
}
return 0;
out_error:
fprintf(stderr, "error: %s\n", strerror(errno));
}
Signed-off-by: Nam Cao <namcao@linutronix.de>
---
include/net/af_unix.h | 5 +++++
kernel/exit.c | 2 ++
net/unix/af_unix.h | 1 -
3 files changed, 7 insertions(+), 1 deletion(-)
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 34f53dde65ce..686f6f1d1c21 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -14,11 +14,16 @@
#if IS_ENABLED(CONFIG_UNIX)
struct unix_sock *unix_get_socket(struct file *filp);
+void unix_schedule_gc(struct user_struct *user);
#else
static inline struct unix_sock *unix_get_socket(struct file *filp)
{
return NULL;
}
+
+static inline void unix_schedule_gc(struct user_struct *user)
+{
+}
#endif
struct unix_address {
diff --git a/kernel/exit.c b/kernel/exit.c
index 2c0b1c02920f..ba9404844523 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -71,6 +71,7 @@
#include <linux/unwind_deferred.h>
#include <linux/uaccess.h>
#include <linux/pidfs.h>
+#include <net/af_unix.h>
#include <uapi/linux/wait.h>
@@ -1002,6 +1003,7 @@ void __noreturn do_exit(long code)
exit_sem(tsk);
exit_shm(tsk);
exit_files(tsk);
+ unix_schedule_gc(NULL); /* Must be after exit_files() */
exit_fs(tsk);
if (group_dead)
disassociate_ctty(1);
diff --git a/net/unix/af_unix.h b/net/unix/af_unix.h
index 8119dbeef3a3..fc4c59893124 100644
--- a/net/unix/af_unix.h
+++ b/net/unix/af_unix.h
@@ -30,7 +30,6 @@ void unix_update_edges(struct unix_sock *receiver);
int unix_prepare_fpl(struct scm_fp_list *fpl);
void unix_destroy_fpl(struct scm_fp_list *fpl);
void unix_peek_fpl(struct scm_fp_list *fpl);
-void unix_schedule_gc(struct user_struct *user);
/* SOCK_DIAG */
long unix_inq_len(struct sock *sk);
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit
2026-07-17 6:27 ` [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit Nam Cao
@ 2026-07-17 6:45 ` Nam Cao
2026-07-18 6:27 ` sashiko-bot
1 sibling, 0 replies; 8+ messages in thread
From: Nam Cao @ 2026-07-17 6:45 UTC (permalink / raw)
To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel,
linux-rt-devel
Nam Cao <namcao@linutronix.de> writes:
> @@ -1002,6 +1003,7 @@ void __noreturn do_exit(long code)
> exit_sem(tsk);
> exit_shm(tsk);
> exit_files(tsk);
> + unix_schedule_gc(NULL); /* Must be after exit_files() */
Argh, I just realized that exit_files() is deferred to task work, so
this must be after exit_task_work().
I will send v3 in a few days, if there is no other comment.
Nam
^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit
2026-07-17 6:27 ` [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit Nam Cao
2026-07-17 6:45 ` Nam Cao
@ 2026-07-18 6:27 ` sashiko-bot
1 sibling, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-18 6:27 UTC (permalink / raw)
To: Nam Cao; +Cc: linux-kernel, linux-rt-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [Medium] The patch reuses `unix_schedule_gc()` in `do_exit()`, but its `gc_in_progress` optimization introduces a race condition that leaks cyclic sockets during concurrent task exits.
--
--- Patch [1]: [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit ---
commit a622ecdd6ba9403d5e403273ed947355870471a8
Author: Nam Cao <namcao@linutronix.de>
af_unix: Schedule the garbage collector at task exit
This commit aims to resolve an issue where dead cyclic reference AF_UNIX
sockets stay behind indefinitely when a task exits. It does so by
scheduling the garbage collector during task exit, after the task's file
descriptors have been closed.
> diff --git a/kernel/exit.c b/kernel/exit.c
> --- a/kernel/exit.c
> +++ b/kernel/exit.c
> @@ -997,6 +998,7 @@ void __noreturn do_exit(long code)
> exit_sem(tsk);
> exit_shm(tsk);
> exit_files(tsk);
> + unix_schedule_gc(NULL); /* Must be after exit_files() */
[Severity: Medium]
Will this race with concurrent task exits and leak cyclic sockets?
In net/unix/garbage.c, unix_schedule_gc() only queues the work if a garbage
collection is not already running:
void unix_schedule_gc(void)
{
if (!READ_ONCE(gc_in_progress))
queue_work(system_dfl_wq, &unix_gc_work);
}
If Task A and Task B exit concurrently with dead cyclic sockets:
Task A calls unix_schedule_gc() and triggers the work, which sets
gc_in_progress to true.
Task B then calls unix_schedule_gc() while the GC is running, observes
gc_in_progress is true, and silently skips queuing the work.
Task B's newly dead cycles might be missed by the currently running GC pass.
Since no new GC is queued by Task B, won't those sockets stay behind
indefinitely until another unrelated task exits, defeating the purpose of
the patch for concurrent exits?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784268931.git.namcao@linutronix.de?part=1
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next v2 2/3] af_unix: Do not wait for garbage collector in sendmsg()
2026-07-17 6:27 [PATCH net-next v2 0/3] af_unix: Fix priority inversion issue Nam Cao
2026-07-17 6:27 ` [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit Nam Cao
@ 2026-07-17 6:27 ` Nam Cao
2026-07-18 6:27 ` sashiko-bot
2026-07-17 6:27 ` [PATCH net-next v2 3/3] af_unix: Clean up unix_schedule_gc() Nam Cao
2 siblings, 1 reply; 8+ messages in thread
From: Nam Cao @ 2026-07-17 6:27 UTC (permalink / raw)
To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel,
linux-rt-devel
Cc: Nam Cao
AF_UNIX sockets' sendmsg() schedules and blocks on the garbage collector if
user has too many inflight unix sockets and there is cyclic reference in
the system. This causes real-time issues, as cyclic reference can be
created by any task in the system, and high priority tasks who do need to
send lots of AF_UNIX sockets get blocked by the garbage collector which
runs as workqueue, causing a priority inversion scenario.
The reason for blocking on garbage collector goes back to 2008, when
it was reported that "Local/unprivileged users can cause soft lockups
and take out system processes by triggering the OOM killer":
https://bugzilla.redhat.com/show_bug.cgi?id=470201
The soft lockup was because a process can keep queueing AF_UNIX sockets to
another process that is exiting. Back in 2008, the garbage collector was
run synchronously by the exiting process, therefore keep queueing AF_UNIX
sockets blocks that process from exiting.
The solution to that issue was forcing sendmsg() to wait for ongoing
garbage collector.
The OOM killer issue was brought up again in 2010:
https://lore.kernel.org/lkml/AANLkTi=Q967xpX0KLMwX-=_4_1AKO5wjHEuJ1TrNjCj9@mail.gmail.com/
To resolve that report, beside blocking on the garbage collector, sendmsg()
also schedules the garbage collector if the number of inflight AF_UNIX
sockets in the system is too high.
Then in 2015, once again, the OOM killer problem was brought up:
https://lore.kernel.org/lkml/20151228141435.GA13351@1wt.eu/
That time, the issue was resolved by disallowing a user from having more
inflight AF_UNIX sockets than their RLIMIT_NOFILE. That was done by commit
712f4aad406b ("unix: properly account for FDs passed over unix sockets")
and commit 415e3d3e90ce ("unix: correctly track in-flight fds in sending
process user_struct").
Now, sendmsg() does not have to block on the garbage collector anymore,
because:
- The OOM killer issue has already been addressed by checking
RLIMIT_NOFILE.
- The soft lockup issue is no longer relevant, because the garbage
collector now runs asynchronously since commit d9f21b361333 ("af_unix:
Try to run GC async.")
Therefore, remove that to prevent priority inversion. Running all the
reproducers from the mentioned bug reports after this patch, no problem is
observed.
Signed-off-by: Nam Cao <namcao@linutronix.de>
---
net/unix/garbage.c | 2 --
1 file changed, 2 deletions(-)
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index 0783555e2526..f180c59b3da9 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -300,8 +300,6 @@ int unix_prepare_fpl(struct scm_fp_list *fpl)
if (!fpl->edges)
goto err;
- unix_schedule_gc(fpl->user);
-
return 0;
err:
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH net-next v2 2/3] af_unix: Do not wait for garbage collector in sendmsg()
2026-07-17 6:27 ` [PATCH net-next v2 2/3] af_unix: Do not wait for garbage collector in sendmsg() Nam Cao
@ 2026-07-18 6:27 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-18 6:27 UTC (permalink / raw)
To: Nam Cao; +Cc: linux-rt-devel, linux-kernel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Removing `unix_schedule_gc()` from `unix_prepare_fpl()` prevents proactive garbage collection of cyclic AF_UNIX FDs.
--
--- Patch [2]: [PATCH net-next v2 2/3] af_unix: Do not wait for garbage collector in sendmsg() ---
commit beb11f61735b0b337472eab3fcd9041c544b30de
Author: Nam Cao <namcao@linutronix.de>
af_unix: Do not wait for garbage collector in sendmsg()
This commit removes the unix_schedule_gc() call from unix_prepare_fpl()
during sendmsg(). The patch notes that previous soft lockup and OOM issues
are already mitigated by async garbage collection and RLIMIT_NOFILE checks.
> diff --git a/net/unix/garbage.c b/net/unix/garbage.c
> index 0783555e25266..f180c59b3da9d 100644
> --- a/net/unix/garbage.c
> +++ b/net/unix/garbage.c
> @@ -300,8 +300,6 @@ int unix_prepare_fpl(struct scm_fp_list *fpl)
> if (!fpl->edges)
> goto err;
>
> - unix_schedule_gc(fpl->user);
> -
> return 0;
>
> err:
[Severity: High]
Does removing the unix_schedule_gc() call from unix_prepare_fpl() prevent
proactive garbage collection of cyclic AF_UNIX file descriptors?
If a long-running application creates a cycle of AF_UNIX sockets and closes
its own file descriptors to them, the cycle maintains a positive f_count.
Without this call triggering the garbage collector, the uncollected cyclic
sockets could accumulate until the application reaches its RLIMIT_NOFILE
quota, causing subsequent socket operations to fail.
Additionally, does this change leave the user parameter logic in
unix_schedule_gc() as dead code?
If the remaining callers now only pass NULL, the user quota checks in
unix_schedule_gc() will no longer be reachable:
net/unix/garbage.c:unix_schedule_gc() {
...
if (user &&
READ_ONCE(user->unix_inflight) < UNIX_INFLIGHT_SANE_USER)
return;
...
}
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784268931.git.namcao@linutronix.de?part=2
^ permalink raw reply [flat|nested] 8+ messages in thread
* [PATCH net-next v2 3/3] af_unix: Clean up unix_schedule_gc()
2026-07-17 6:27 [PATCH net-next v2 0/3] af_unix: Fix priority inversion issue Nam Cao
2026-07-17 6:27 ` [PATCH net-next v2 1/3] af_unix: Schedule the garbage collector at task exit Nam Cao
2026-07-17 6:27 ` [PATCH net-next v2 2/3] af_unix: Do not wait for garbage collector in sendmsg() Nam Cao
@ 2026-07-17 6:27 ` Nam Cao
2026-07-18 6:27 ` sashiko-bot
2 siblings, 1 reply; 8+ messages in thread
From: Nam Cao @ 2026-07-17 6:27 UTC (permalink / raw)
To: Kuniyuki Iwashima, David S . Miller, Eric Dumazet,
Jakub Kicinski, Paolo Abeni, Simon Horman, netdev, linux-kernel,
linux-rt-devel
Cc: Nam Cao
unix_schedule_gc() is only called with NULL as an argument.
Simplify by deleting the parameter.
Signed-off-by: Nam Cao <namcao@linutronix.de>
---
include/net/af_unix.h | 4 ++--
kernel/exit.c | 2 +-
net/unix/af_unix.c | 2 +-
net/unix/garbage.c | 14 +-------------
4 files changed, 5 insertions(+), 17 deletions(-)
diff --git a/include/net/af_unix.h b/include/net/af_unix.h
index 686f6f1d1c21..1dfdc0720052 100644
--- a/include/net/af_unix.h
+++ b/include/net/af_unix.h
@@ -14,14 +14,14 @@
#if IS_ENABLED(CONFIG_UNIX)
struct unix_sock *unix_get_socket(struct file *filp);
-void unix_schedule_gc(struct user_struct *user);
+void unix_schedule_gc(void);
#else
static inline struct unix_sock *unix_get_socket(struct file *filp)
{
return NULL;
}
-static inline void unix_schedule_gc(struct user_struct *user)
+static inline void unix_schedule_gc(void)
{
}
#endif
diff --git a/kernel/exit.c b/kernel/exit.c
index ba9404844523..764a911f111e 100644
--- a/kernel/exit.c
+++ b/kernel/exit.c
@@ -1003,7 +1003,7 @@ void __noreturn do_exit(long code)
exit_sem(tsk);
exit_shm(tsk);
exit_files(tsk);
- unix_schedule_gc(NULL); /* Must be after exit_files() */
+ unix_schedule_gc(); /* Must be after exit_files() */
exit_fs(tsk);
if (group_dead)
disassociate_ctty(1);
diff --git a/net/unix/af_unix.c b/net/unix/af_unix.c
index f7a9d55eee8a..759db734a866 100644
--- a/net/unix/af_unix.c
+++ b/net/unix/af_unix.c
@@ -733,7 +733,7 @@ static void unix_release_sock(struct sock *sk, int embrion)
/* ---- Socket is dead now and most probably destroyed ---- */
- unix_schedule_gc(NULL);
+ unix_schedule_gc();
}
struct unix_peercred {
diff --git a/net/unix/garbage.c b/net/unix/garbage.c
index f180c59b3da9..d46aeb9d2051 100644
--- a/net/unix/garbage.c
+++ b/net/unix/garbage.c
@@ -635,23 +635,11 @@ static void unix_gc(struct work_struct *work)
static DECLARE_WORK(unix_gc_work, unix_gc);
-#define UNIX_INFLIGHT_SANE_USER (SCM_MAX_FD * 8)
-
-void unix_schedule_gc(struct user_struct *user)
+void unix_schedule_gc(void)
{
if (READ_ONCE(unix_graph_state) == UNIX_GRAPH_NOT_CYCLIC)
return;
- /* Penalise users who want to send AF_UNIX sockets
- * but whose sockets have not been received yet.
- */
- if (user &&
- READ_ONCE(user->unix_inflight) < UNIX_INFLIGHT_SANE_USER)
- return;
-
if (!READ_ONCE(gc_in_progress))
queue_work(system_dfl_wq, &unix_gc_work);
-
- if (user && READ_ONCE(unix_graph_cyclic_sccs))
- flush_work(&unix_gc_work);
}
--
2.47.3
^ permalink raw reply [flat|nested] 8+ messages in thread* Re: [PATCH net-next v2 3/3] af_unix: Clean up unix_schedule_gc()
2026-07-17 6:27 ` [PATCH net-next v2 3/3] af_unix: Clean up unix_schedule_gc() Nam Cao
@ 2026-07-18 6:27 ` sashiko-bot
0 siblings, 0 replies; 8+ messages in thread
From: sashiko-bot @ 2026-07-18 6:27 UTC (permalink / raw)
To: Nam Cao; +Cc: linux-kernel, linux-rt-devel
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
- [High] Unconditional garbage collector scheduling in `do_exit()` causes severe lock contention and system-wide DoS when a live AF_UNIX cycle exists.
--
--- Patch [3]: [PATCH net-next v2 3/3] af_unix: Clean up unix_schedule_gc() ---
commit 649d449e71c2d27619a029df46ed559c6fa2ae0c
Author: Nam Cao <namcao@linutronix.de>
af_unix: Clean up unix_schedule_gc()
This patch simplifies unix_schedule_gc() by removing its parameter,
as it was only called with NULL. The user struct parameter and the
logic depending on it are removed.
[ ... ]
> diff --git a/kernel/exit.c b/kernel/exit.c
> index b507b2a24693a..f3bee7cc637d0 100644
> --- a/kernel/exit.c
> +++ b/kernel/exit.c
> @@ -998,7 +998,7 @@ void __noreturn do_exit(long code)
> exit_sem(tsk);
> exit_shm(tsk);
> exit_files(tsk);
> - unix_schedule_gc(NULL); /* Must be after exit_files() */
> + unix_schedule_gc(); /* Must be after exit_files() */
[Severity: High]
Does this call trigger the garbage collector for every exiting task in the
system?
If an unprivileged user creates a live AF_UNIX cyclic reference and leaves
them open, while queueing a large number of normal inflight sockets up to
RLIMIT_NOFILE, the live cycle causes unix_graph_state to persistently be
UNIX_GRAPH_CYCLIC.
Every task exit would then constantly queue unix_gc_work, which executes
unix_walk_scc_fast() under the unix_gc_lock spinlock, iterating over all
inflight sockets:
net/unix/garbage.c:unix_walk_scc_fast() {
...
while (!list_empty(&unix_unvisited_vertices)) {
...
}
Could this O(N) loop iterating over all inflight sockets while holding the
unix_gc_lock cause severe lock contention and a system-wide performance
degradation for any AF_UNIX operations like sendmsg and recvmsg that block
on this lock?
--
Sashiko AI review · https://sashiko.dev/#/patchset/cover.1784268931.git.namcao@linutronix.de?part=3
^ permalink raw reply [flat|nested] 8+ messages in thread