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) <[email protected]>
---
 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(&current->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


Reply via email to