[PATCH RFC POC 02/50] entry: commit fds on syscall exit
From: Christian Brauner
Date: Tue Sep 15 2026 - 07:38:42 EST
Reserving a descriptor with fd_prepare() marks the task with
SYSCALL_WORK_FD_SLOTS. The syscall exit path already tests the
syscall work bits so a syscall that deferred nothing pays nothing new
and one that did takes the slow path. syscall_exit_work() commits the
reservations before audit and ptrace.
Architectures without the generic entry code grow TIF_FD_SLOTS bit in
their syscall exit work in the following patches.
Signed-off-by: Christian Brauner (Amutable) <brauner@xxxxxxxxxx>
---
fs/file.c | 24 ++++++++++++++++++++++++
include/linux/entry-common.h | 8 +++++++-
include/linux/file.h | 2 ++
include/linux/thread_info.h | 2 ++
4 files changed, 35 insertions(+), 1 deletion(-)
diff --git a/fs/file.c b/fs/file.c
index 90351241bb07..6fa481d63dcf 100644
--- a/fs/file.c
+++ b/fs/file.c
@@ -23,6 +23,7 @@
#include <linux/file_ref.h>
#include <net/sock.h>
#include <linux/init_task.h>
+#include <asm/syscall.h>
#include "internal.h"
@@ -643,6 +644,19 @@ static void fd_release(unsigned int fd)
/* Enough for SCM_MAX_FD, and a page of slots on 4K pages. */
#define FD_SLOTS_SPILL_MIN 256
+/* Make the syscall exit path call fd_slots_commit(). */
+#if defined(CONFIG_GENERIC_ENTRY)
+#define fd_slots_set_work() set_syscall_work(FD_SLOTS)
+#define fd_slots_clear_work() clear_syscall_work(FD_SLOTS)
+#elif defined(TIF_FD_SLOTS)
+#define fd_slots_set_work() set_thread_flag(TIF_FD_SLOTS)
+#define fd_slots_clear_work() clear_thread_flag(TIF_FD_SLOTS)
+#else
+/* Nothing commits until the architecture provides the flag. */
+#define fd_slots_set_work() do { } while (0)
+#define fd_slots_clear_work() do { } while (0)
+#endif
+
static struct fd_slot *fd_slot(struct fd_slots *slots, unsigned int idx)
{
if (idx < FD_SLOTS_INLINE)
@@ -690,6 +704,8 @@ static struct fd_slot *fd_slot_record(int fd)
}
ACCESS_PRIVATE(slot, fd) = fd;
ACCESS_PRIVATE(slot, file) = NULL;
+ if (!idx)
+ fd_slots_set_work();
slots->nr = idx + 1;
return slot;
}
@@ -809,6 +825,14 @@ static __always_inline void fd_slots_finish(struct fd_slots *slots, bool failed)
else
fd_slots_drop(slots);
slots->nr = 0;
+ fd_slots_clear_work();
+}
+
+/* Syscall exit hook, keyed on the return value the caller will see. */
+void fd_slots_commit(struct pt_regs *regs)
+{
+ fd_slots_finish(¤t->fd_slots,
+ syscall_get_error(current, regs) != 0);
}
/* Install or drop the prepared descriptors based on @ret. */
diff --git a/include/linux/entry-common.h b/include/linux/entry-common.h
index 6574b7183c01..2da30e50cf59 100644
--- a/include/linux/entry-common.h
+++ b/include/linux/entry-common.h
@@ -3,6 +3,7 @@
#define __LINUX_ENTRYCOMMON_H
#include <linux/audit.h>
+#include <linux/file.h>
#include <linux/irq-entry-common.h>
#include <linux/livepatch.h>
#include <linux/ptrace.h>
@@ -36,7 +37,8 @@
SYSCALL_WORK_SYSCALL_TRACE | \
SYSCALL_WORK_SYSCALL_AUDIT | \
SYSCALL_WORK_SYSCALL_USER_DISPATCH | \
- SYSCALL_WORK_SYSCALL_EXIT_TRAP)
+ SYSCALL_WORK_SYSCALL_EXIT_TRAP | \
+ SYSCALL_WORK_FD_SLOTS)
/**
* arch_ptrace_report_syscall_permit_entry - Architecture specific wrapper for
@@ -245,6 +247,10 @@ static __always_inline void syscall_exit_work(struct pt_regs *regs, unsigned lon
{
bool step;
+ /* Install or drop the descriptors the syscall prepared. */
+ if (work & SYSCALL_WORK_FD_SLOTS)
+ fd_slots_commit(regs);
+
/*
* If the syscall was rolled back due to syscall user dispatching,
* then the tracers below are not invoked for the same reason as
diff --git a/include/linux/file.h b/include/linux/file.h
index fe2893eea945..04dd85cdd9bf 100644
--- a/include/linux/file.h
+++ b/include/linux/file.h
@@ -92,6 +92,8 @@ extern int __get_unused_fd_flags(unsigned flags, unsigned long nofile);
extern int get_unused_fd_flags(unsigned flags);
extern void put_unused_fd(unsigned int fd);
void __fd_slots_commit(long ret);
+struct pt_regs;
+void fd_slots_commit(struct pt_regs *regs);
void exit_fd_slots(void);
DEFINE_CLASS(get_unused_fd, int, if (_T >= 0) put_unused_fd(_T),
diff --git a/include/linux/thread_info.h b/include/linux/thread_info.h
index 307b8390fc67..c80a87444286 100644
--- a/include/linux/thread_info.h
+++ b/include/linux/thread_info.h
@@ -47,6 +47,7 @@ enum syscall_work_bit {
SYSCALL_WORK_BIT_SYSCALL_USER_DISPATCH,
SYSCALL_WORK_BIT_SYSCALL_EXIT_TRAP,
SYSCALL_WORK_BIT_SYSCALL_RSEQ_SLICE,
+ SYSCALL_WORK_BIT_FD_SLOTS,
};
#define SYSCALL_WORK_SECCOMP BIT(SYSCALL_WORK_BIT_SECCOMP)
@@ -57,6 +58,7 @@ enum syscall_work_bit {
#define SYSCALL_WORK_SYSCALL_USER_DISPATCH BIT(SYSCALL_WORK_BIT_SYSCALL_USER_DISPATCH)
#define SYSCALL_WORK_SYSCALL_EXIT_TRAP BIT(SYSCALL_WORK_BIT_SYSCALL_EXIT_TRAP)
#define SYSCALL_WORK_SYSCALL_RSEQ_SLICE BIT(SYSCALL_WORK_BIT_SYSCALL_RSEQ_SLICE)
+#define SYSCALL_WORK_FD_SLOTS BIT(SYSCALL_WORK_BIT_FD_SLOTS)
#endif
#include <asm/thread_info.h>
--
2.53.0