From e63ebc4a8edf21556a82939938405cad38e28af4 Mon Sep 17 00:00:00 2001 From: dujunbao <14829755+du-junbao@user.noreply.gitee.com> Date: Tue, 22 Sep 2026 21:37:27 +0800 Subject: [PATCH] tkernel: ttools: wait for in-flight ptrace hook callers on unload sys_ptrace() and the compat variant call ptrace_pre_hook, a bare function pointer exported by the ttools module, without holding any reference to that module and without any synchronisation. ttools_exit() only stores NULL and calls smp_call_function() to flush the icache; that does not wait for a CPU which has already loaded the pointer and is about to enter ttools_ptrace_hook(), nor for a CPU which is already executing inside it. ttools is built as a loadable module (TKERNEL_TTOOLS is tristate), so rmmod can complete while another process is in ptrace: module_exit() returns, the module's text and data are freed, and the in-flight caller keeps running from freed memory. The hook also touches module data (ttools_pids_lock, ttools_protected_pids), which is freed the same way. The aegis hook in the same tree waits for its in-flight users before it lets the module go; the ptrace hook has no such protection. Make the hook call an RCU read-side critical section and have the unload path wait for a grace period after publishing NULL, so no CPU can be inside the hook once module_exit() proceeds. The hook does not sleep. Fixes: c9c30816bbb4 ("tkernel: ttools: add ttools module to support ptrace protect") --- kernel/ptrace.c | 36 ++++++++++++++++++++++----- kernel/tkernel/ttools/ttools_module.c | 6 +++-- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/kernel/ptrace.c b/kernel/ptrace.c index 207ea4734384..1adec072b048 100644 --- a/kernel/ptrace.c +++ b/kernel/ptrace.c @@ -1285,6 +1285,8 @@ EXPORT_SYMBOL(ptrace_pre_hook); SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, unsigned long, data) { + int (*hook)(long request, long pid, struct task_struct *task, long addr, + long data); struct task_struct *child; long ret; @@ -1299,11 +1301,21 @@ SYSCALL_DEFINE4(ptrace, long, request, long, pid, unsigned long, addr, goto out; } - if (ptrace_pre_hook) { - ret = ptrace_pre_hook(request, pid, child, addr, data); - if (ret) + /* + * The hook is provided by a loadable module and disappears when that + * module is unloaded, so the call has to be inside an RCU read-side + * critical section; the unload path waits for a grace period. + */ + rcu_read_lock(); + hook = READ_ONCE(ptrace_pre_hook); + if (hook) { + ret = hook(request, pid, child, addr, data); + if (ret) { + rcu_read_unlock(); goto out_put_task_struct; + } } + rcu_read_unlock(); if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) { ret = ptrace_attach(child, request, addr, data); @@ -1430,6 +1442,8 @@ int compat_ptrace_request(struct task_struct *child, compat_long_t request, COMPAT_SYSCALL_DEFINE4(ptrace, compat_long_t, request, compat_long_t, pid, compat_long_t, addr, compat_long_t, data) { + int (*hook)(long request, long pid, struct task_struct *task, long addr, + long data); struct task_struct *child; long ret; @@ -1444,11 +1458,21 @@ COMPAT_SYSCALL_DEFINE4(ptrace, compat_long_t, request, compat_long_t, pid, goto out; } - if (ptrace_pre_hook) { - ret = ptrace_pre_hook(request, pid, child, addr, data); - if (ret) + /* + * The hook is provided by a loadable module and disappears when that + * module is unloaded, so the call has to be inside an RCU read-side + * critical section; the unload path waits for a grace period. + */ + rcu_read_lock(); + hook = READ_ONCE(ptrace_pre_hook); + if (hook) { + ret = hook(request, pid, child, addr, data); + if (ret) { + rcu_read_unlock(); goto out_put_task_struct; + } } + rcu_read_unlock(); if (request == PTRACE_ATTACH || request == PTRACE_SEIZE) { ret = ptrace_attach(child, request, addr, data); diff --git a/kernel/tkernel/ttools/ttools_module.c b/kernel/tkernel/ttools/ttools_module.c index e1302187a15b..385eeaf70e2d 100644 --- a/kernel/tkernel/ttools/ttools_module.c +++ b/kernel/tkernel/ttools/ttools_module.c @@ -258,7 +258,7 @@ static int ttools_init(void) if (ret) return ret; - ptrace_pre_hook = ttools_ptrace_hook; + WRITE_ONCE(ptrace_pre_hook, ttools_ptrace_hook); smp_wmb(); smp_call_function(flush_icache_1, NULL, 1); pr_info("ttools " TTOOLS_VER " loaded\n"); @@ -267,9 +267,11 @@ static int ttools_init(void) static void ttools_exit(void) { - ptrace_pre_hook = NULL; + WRITE_ONCE(ptrace_pre_hook, NULL); smp_wmb(); smp_call_function(flush_icache_1, NULL, 1); + /* wait for callers which are still inside the hook */ + synchronize_rcu(); misc_deregister(&ttools_dev); ttools_clean_task_list(); pr_info("ttools " TTOOLS_VER " unloaded\n"); -- Gitee