From 96d5d960fef4dee415e109cb1d0c8f3352985d05 Mon Sep 17 00:00:00 2001 From: Anthony Date: Sat, 3 Oct 2026 00:02:40 +0200 Subject: [PATCH 1/4] binder: fix NULL lock dereference in shrinker on Linux >= 6.13 Since Linux 6.13 the list_lru walk callback no longer receives the lru lock; it lives in struct list_lru_one. binder_alloc_free_page_no_lock() passes NULL as the lock, so binder_alloc_free_page() ends up calling spin_unlock(NULL) as soon as kswapd runs the binder shrinker: BUG: kernel NULL pointer dereference, address: 0000000000000000 RIP: 0010:_raw_spin_unlock+0x9/0x40 Call Trace: binder_alloc_free_page+0xe1/0x360 [binder_linux] __list_lru_walk_one+0xdf/0x230 list_lru_walk_node+0x52/0x230 binder_shrink_scan+0xa7/0x100 [binder_linux] do_shrink_slab+0x154/0x3a0 ... kswapd+0x1e2/0x360 The machine then hangs, typically under memory pressure, right after resuming from suspend, or while writing a hibernation image. Fall back to &lru->lock when no lock is passed, as upstream binder does. --- binder/binder_alloc.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/binder/binder_alloc.c b/binder/binder_alloc.c index 45c2c56..9afc7bb 100644 --- a/binder/binder_alloc.c +++ b/binder/binder_alloc.c @@ -1003,6 +1003,10 @@ enum lru_status binder_alloc_free_page(struct list_head *item, size_t index; struct vm_area_struct *vma; + /* Since 6.13 the list_lru callback gets no lock; it lives in lru. */ + if (!lock) + lock = &lru->lock; + alloc = page->alloc; if (!mutex_trylock(&alloc->mutex)) goto err_get_alloc_mutex_failed; From 0ee331db893d515c88f65a6bf3f72ff2cc6fba4a Mon Sep 17 00:00:00 2001 From: Anthony Date: Tue, 13 Oct 2026 22:25:42 +0200 Subject: [PATCH 2/4] binder: don't relock after LRU_REMOVED_RETRY on Linux >= 6.13 Since 6.13 the list_lru walker reacquires the lock itself after LRU_REMOVED_RETRY. Taking it in the callback makes the next lock_list_lru_of_memcg() spin forever, leaving kswapd at 100% CPU in binder_shrink_scan(). --- binder/binder_alloc.c | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/binder/binder_alloc.c b/binder/binder_alloc.c index 9afc7bb..5113690 100644 --- a/binder/binder_alloc.c +++ b/binder/binder_alloc.c @@ -1048,8 +1048,16 @@ enum lru_status binder_alloc_free_page(struct list_head *item, trace_binder_unmap_kernel_end(alloc, index); - spin_lock(lock); mutex_unlock(&alloc->mutex); +#if (LINUX_VERSION_CODE < KERNEL_VERSION(6, 13, 0) && \ + !defined(RHEL_10_1_BACKPORTS)) + /* Before 6.13, LRU_REMOVED_RETRY means the lock was reacquired. */ + spin_lock(lock); +#endif + /* + * Since 6.13 the walker relocks itself after LRU_REMOVED_RETRY; + * taking the lock here would make it spin forever (kswapd at 100%). + */ return LRU_REMOVED_RETRY; err_mmap_read_lock_failed: From 63e80d8ea7267ca66b255f840c210d1f53ff0fd3 Mon Sep 17 00:00:00 2001 From: Anthony Date: Tue, 13 Oct 2026 22:28:42 +0200 Subject: [PATCH 3/4] binder: fix kallsyms_lookup_name address with X86_KERNEL_IBT With IBT the kprobe address skips the ENDBR64 at function entry, so calling it indirectly raises a control-protection fault (kernel BUG at arch/x86/kernel/cet.c) the first time binderfs is mounted. Step back over the ENDBR64 when it is there. --- binder/deps.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/binder/deps.c b/binder/deps.c index 63accc0..cc2da65 100644 --- a/binder/deps.c +++ b/binder/deps.c @@ -42,6 +42,20 @@ static kallsyms_lookup_name_t get_kallsyms_lookup_name_ptr(void) addr = (kallsyms_lookup_name_t) probe.addr; unregister_kprobe(&probe); +#ifdef CONFIG_X86_KERNEL_IBT + /* + * With IBT, kprobes skip the function's ENDBR64, so probe.addr is 4 + * bytes past the entry. An indirect call there raises #CP; step back. + */ + { + const u8 endbr64[] = { 0xf3, 0x0f, 0x1e, 0xfa }; + u8 *p = (u8 *)addr - sizeof(endbr64); + + if (!memcmp(p, endbr64, sizeof(endbr64))) + addr = (kallsyms_lookup_name_t)p; + } +#endif + return addr; } #endif From a59d8a3b10deb89cdad3682bc74ef35a5c8865b1 Mon Sep 17 00:00:00 2001 From: Anthony Date: Tue, 13 Oct 2026 22:39:31 +0200 Subject: [PATCH 4/4] binder: call looked-up kernel functions through a push+ret trampoline with IBT The kernel seals the ENDBR64 of functions it never calls indirectly (kallsyms_lookup_name, can_nice, the security_binder_* hooks...), so stepping back over ENDBR64 is not enough: there is none left, and the indirect call still raises #CP when binderfs is mounted. Reach these functions with push+ret, which IBT does not track. --- binder/deps.c | 72 ++++++++++++++++++++++++++++++++------------------- 1 file changed, 45 insertions(+), 27 deletions(-) diff --git a/binder/deps.c b/binder/deps.c index cc2da65..6c7a2af 100644 --- a/binder/deps.c +++ b/binder/deps.c @@ -9,11 +9,43 @@ #include #include #include +#include +#include #include #include #include "deps.h" +#ifdef CONFIG_X86_KERNEL_IBT +/* + * With IBT the kernel seals the ENDBR64 of functions it never calls + * indirectly (kallsyms_lookup_name, can_nice, the security_binder_* hooks...), + * so an indirect call to an address looked up here raises #CP (kernel BUG at + * arch/x86/kernel/cet.c). Reach them with push+ret instead, which IBT does + * not track; the callee then returns straight to our caller. + */ +unsigned long binder_ibt_call(void *fn, unsigned long a, unsigned long b, + unsigned long c, unsigned long d); +asm(".pushsection .text\n" + ".type binder_ibt_call, @function\n" + "binder_ibt_call:\n" + " mov %rdi, %r11\n" + " mov %rsi, %rdi\n" + " mov %rdx, %rsi\n" + " mov %rcx, %rdx\n" + " mov %r8, %rcx\n" + " push %r11\n" + " " ASM_RET + ".size binder_ibt_call, .-binder_ibt_call\n" + ".popsection\n"); +STACK_FRAME_NON_STANDARD(binder_ibt_call); +#define IBT_ARGS(a, b, c, d, ...) \ + (unsigned long)(a), (unsigned long)(b), (unsigned long)(c), (unsigned long)(d) +#define deps_call(fn, ...) binder_ibt_call((void *)(fn), IBT_ARGS(__VA_ARGS__, 0, 0, 0, 0)) +#else +#define deps_call(fn, ...) (fn)(__VA_ARGS__) +#endif + #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,7,0)) #ifndef CONFIG_KPROBES @@ -42,20 +74,6 @@ static kallsyms_lookup_name_t get_kallsyms_lookup_name_ptr(void) addr = (kallsyms_lookup_name_t) probe.addr; unregister_kprobe(&probe); -#ifdef CONFIG_X86_KERNEL_IBT - /* - * With IBT, kprobes skip the function's ENDBR64, so probe.addr is 4 - * bytes past the entry. An indirect call there raises #CP; step back. - */ - { - const u8 endbr64[] = { 0xf3, 0x0f, 0x1e, 0xfa }; - u8 *p = (u8 *)addr - sizeof(endbr64); - - if (!memcmp(p, endbr64, sizeof(endbr64))) - addr = (kallsyms_lookup_name_t)p; - } -#endif - return addr; } #endif @@ -78,7 +96,7 @@ static unsigned long kallsyms_lookup_name_wrapper(const char *name) if (!func_ptr) func_ptr = get_kallsyms_lookup_name_ptr(); - return func_ptr(name); + return deps_call(func_ptr, name); #else return kallsyms_lookup_name(name); #endif @@ -111,9 +129,9 @@ int __close_fd_get_file(unsigned int fd, struct file **res) #endif #if (LINUX_VERSION_CODE >= KERNEL_VERSION(5,19,0)) - return close_fd_get_file_ptr(fd); + return (struct file *)deps_call(close_fd_get_file_ptr, fd); #else - return close_fd_get_file_ptr(fd, res); + return (int)deps_call(close_fd_get_file_ptr, fd, res); #endif } @@ -123,7 +141,7 @@ int can_nice(const struct task_struct *p, const int nice) { if (!can_nice_ptr) can_nice_ptr = kallsyms_lookup_name_wrapper("can_nice"); - return can_nice_ptr(p, nice); + return (int)deps_call(can_nice_ptr, p, nice); } static void (*mmput_async_ptr)(struct mm_struct *mm) = NULL; @@ -132,7 +150,7 @@ void mmput_async(struct mm_struct *mm) { if (!mmput_async_ptr) mmput_async_ptr = kallsyms_lookup_name_wrapper("mmput_async"); - return mmput_async_ptr(mm); + deps_call(mmput_async_ptr, mm); } static int (*security_binder_set_context_mgr_ptr)(struct task_struct *mgr) = NULL; @@ -141,7 +159,7 @@ int security_binder_set_context_mgr(struct task_struct *mgr) { if (!security_binder_set_context_mgr_ptr) security_binder_set_context_mgr_ptr = kallsyms_lookup_name_wrapper("security_binder_set_context_mgr"); - return security_binder_set_context_mgr_ptr(mgr); + return (int)deps_call(security_binder_set_context_mgr_ptr, mgr); } static int (*security_binder_transaction_ptr)(struct task_struct *from, struct task_struct *to) = NULL; @@ -150,7 +168,7 @@ int security_binder_transaction(struct task_struct *from, struct task_struct *to { if (!security_binder_transaction_ptr) security_binder_transaction_ptr = kallsyms_lookup_name_wrapper("security_binder_transaction"); - return security_binder_transaction_ptr(from, to); + return (int)deps_call(security_binder_transaction_ptr, from, to); } static int (*security_binder_transfer_binder_ptr)(struct task_struct *from, struct task_struct *to) = NULL; @@ -159,7 +177,7 @@ int security_binder_transfer_binder(struct task_struct *from, struct task_struct { if (!security_binder_transfer_binder_ptr) security_binder_transfer_binder_ptr = kallsyms_lookup_name_wrapper("security_binder_transfer_binder"); - return security_binder_transfer_binder_ptr(from, to); + return (int)deps_call(security_binder_transfer_binder_ptr, from, to); } static int (*security_binder_transfer_file_ptr)(struct task_struct *from, struct task_struct *to, struct file *file) = NULL; @@ -168,7 +186,7 @@ int security_binder_transfer_file(struct task_struct *from, struct task_struct * { if (!security_binder_transfer_file_ptr) security_binder_transfer_file_ptr = kallsyms_lookup_name_wrapper("security_binder_transfer_file"); - return security_binder_transfer_file_ptr(from, to, file); + return (int)deps_call(security_binder_transfer_file_ptr, from, to, file); } static int (*task_work_add_ptr)(struct task_struct *task, struct callback_head *work, @@ -179,7 +197,7 @@ int task_work_add(struct task_struct *task, struct callback_head *work, { if (!task_work_add_ptr) task_work_add_ptr = kallsyms_lookup_name_wrapper("task_work_add"); - return task_work_add_ptr(task, work, notify); + return (int)deps_call(task_work_add_ptr, task, work, notify); } #if (LINUX_VERSION_CODE >= KERNEL_VERSION(6,3,0)) @@ -189,7 +207,7 @@ void zap_page_range_single(struct vm_area_struct *vma, unsigned long address, un { if (!zap_page_range_single_ptr) zap_page_range_single_ptr = kallsyms_lookup_name_wrapper("zap_page_range_single"); - zap_page_range_single_ptr(vma, address, size, details); + deps_call(zap_page_range_single_ptr, vma, address, size, details); } #else static void (*zap_page_range_ptr)(struct vm_area_struct *, unsigned long, unsigned long) = NULL; @@ -198,7 +216,7 @@ void zap_page_range(struct vm_area_struct *vma, unsigned long address, unsigned { if (!zap_page_range_ptr) zap_page_range_ptr = kallsyms_lookup_name_wrapper("zap_page_range"); - zap_page_range_ptr(vma, address, size); + deps_call(zap_page_range_ptr, vma, address, size); } #endif @@ -208,7 +226,7 @@ void put_ipc_ns(struct ipc_namespace *ns) { if (!put_ipc_ns_ptr) put_ipc_ns_ptr = kallsyms_lookup_name_wrapper("put_ipc_ns"); - put_ipc_ns_ptr(ns); + deps_call(put_ipc_ns_ptr, ns); } static struct ipc_namespace *init_ipc_ns_ptr = NULL;