x86: 32-bit uprobes are broken
Subsystems (7)
Oleg Nesterov
maintainerPaulo reported that uprobing the 32-bit tasks is broken.
This script
#!/usr/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
echo 'void main(void) {}' > TEST.c
# -fcf-protection to ensure that the 1st endbr32 insn can't be eulated gcc -m32 -fcf-protection=branch TEST.c -o test
bpftrace -e 'uprobe:./test:main {}' -c ./test
"hangs", the probed ./test task enters the endless loop.
This patch
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1710,8 +1710,11 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)
if (!area->vaddr) {
/* Try to map as high as possible, this is only a hint. */
+ if (test_thread_flag(TIF_ADDR32))
+ current_thread_info()->status |= TS_COMPAT;
area->vaddr = get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE,
PAGE_SIZE, 0, 0);
+ current_thread_info()->status &= ~TS_COMPAT;
if (IS_ERR_VALUE(area->vaddr)) {
ret = area->vaddr;
goto fail;or this one
--- a/arch/x86/kernel/sys_x86_64.c
+++ b/arch/x86/kernel/sys_x86_64.c
@@ -205,6 +205,8 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr0,
info.low_limit = PAGE_SIZE;
info.high_limit = get_mmap_base(0);
+ if (test_thread_flag(TIF_ADDR32))
+ info.high_limit = current->mm->mmap_compat_base;
if (!(filp && is_file_hugepages(filp))) {
info.start_gap = stack_guard_placement(vm_flags);
info.align_offset = pgoff << PAGE_SHIFT;"fixes" the problem.
-----------------------------------------------------------------------------------------
The problem is that with randomize_va_space == 0 get_unmapped_area(TASK_SIZE - PAGE_SIZE) called by xol_add_vma() can't just return the "addr == TASK_SIZE - PAGE_SIZE" hint, this addr is used by the stack vma.
arch_get_unmapped_area/arch_get_unmapped_area_topdown() doesn't take TIF_ADDR32 into account and in_32bit_syscall() is false, this leads to info.high_limit > TASK_SIZE. get_area() happily returns the "high" address and then get_unmapped_area() returns ENOMEM after the
if (addr > TASK_SIZE - len) return -ENOMEM;
check; TASK_SIZE checks TIF_ADDR32.
handle_swbp() doesn't report this failure (probably it should) and silently restarts the probed insn. Endless loop.
-----------------------------------------------------------------------------------------
I am considering the patch which adds something like
// x86 version will use the TS_COMPAT hack
unsigned long __weak arch_uprobe_area(void)
{
return get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE, PAGE_SIZE, 0, 0);
}but perhaps there is a better solution? perhaps it makes more sense to change arch/x86/kernel/sys_x86_64.c? What is the point of ignoring TIF_ADDR32 if the high adress will be nacked by the "if (addr > TASK_SIZE - len)" check anyway?
Oleg.
Peter Zijlstra
maintainerQuoted 19 lines · Oleg Nesterov
Paulo reported that uprobing the 32-bit tasks is broken.
This script
#!/usr/bin/bash
echo 0 >
/proc/sys/kernel/randomize_va_spaceecho 'void main(void) {}' >
TEST.c# -fcf-protection to ensure that the 1st endbr32 insn can't be eulated gcc -m32 -fcf-protection=branch
TEST.c-o testbpftrace -e 'uprobe:./test:main {}' -c ./test
"hangs", the probed ./test task enters the endless loop.
This patch
Quoted 14 lines
--- a/kernel/events/uprobes.c
+++ b/kernel/events/uprobes.c
@@ -1710,8 +1710,11 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area)
if (!area->vaddr) {
/* Try to map as high as possible, this is only a hint. */
+ if (test_thread_flag(TIF_ADDR32))
+ current_thread_info()->status |= TS_COMPAT;
area->vaddr = get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE,
PAGE_SIZE, 0, 0);
+ current_thread_info()->status &= ~TS_COMPAT;
if (IS_ERR_VALUE(area->vaddr)) {
ret = area->vaddr;
goto fail;Urgh. Also I'm a little bit confused; set_personality_ia32(), which is what sets TIF_ADDR32 (afaict) would also set TS_COMPAT, no?
Quoted 13 lines
or this one
--- a/arch/x86/kernel/sys_x86_64.c +++ b/arch/x86/kernel/sys_x86_64.c @@ -205,6 +205,8 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr0, info.low_limit = PAGE_SIZE; info.high_limit = get_mmap_base(0); + if (test_thread_flag(TIF_ADDR32)) + info.high_limit = current->mm->mmap_compat_base; if (!(filp && is_file_hugepages(filp))) { info.start_gap = stack_guard_placement(vm_flags); info.align_offset = pgoff << PAGE_SHIFT;
This one would be broken if there is ever a case where get_unmapped_area() is called for mm != current->mm.
Ah, but get_mmap_base() seems to already rely on that. What a mess :/
Why can't get_mmap_base() rely on TIF_ADDR32 rather than TS_COMPAT? What funny games are being played here? Mixed mode mm where some threads are 32bit and others are not?
Quoted 29 lines
"fixes" the problem.
-----------------------------------------------------------------------------------------
The problem is that withrandomize_va_space== 0get_unmapped_area(TASK_SIZE - PAGE_SIZE)called byxol_add_vma()can't just return the "addr ==TASK_SIZE-PAGE_SIZE" hint, this addr is used by the stack vma.arch_get_unmapped_area/arch_get_unmapped_area_topdown() doesn't take
TIF_ADDR32into account andin_32bit_syscall()is false, this leads to info.high_limit >TASK_SIZE.get_area()happily returns the "high" address and thenget_unmapped_area()returns ENOMEM after theif (addr > TASK_SIZE - len) return -ENOMEM;check;
TASK_SIZEchecksTIF_ADDR32.
handle_swbp()doesn't report this failure (probably it should) and silently restarts the probed insn. Endless loop.-----------------------------------------------------------------------------------------
I am considering the patch which adds something like// x86 version will use the TS_COMPAT hack unsigned long __weak arch_uprobe_area(void) { return get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE, PAGE_SIZE, 0, 0); }
Quoted 3 lines
but perhaps there is a better solution? perhaps it makes more sense to change
arch/x86/kernel/sys_x86_64.c? What is the point of ignoringTIF_ADDR32if the high adress will be nacked by the "if (addr >TASK_SIZE- len)" check anyway?
To me it seems a bit weird that get_unmapped_area() is so disconnected from mm_struct.
Oleg Nesterov
maintainerQuoted 19 lines · Peter Zijlstra
Quoted 16 lines · Oleg Nesterov
This patch
--- a/kernel/events/uprobes.c +++ b/kernel/events/uprobes.c @@ -1710,8 +1710,11 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area) if (!area->vaddr) { /* Try to map as high as possible, this is only a hint. */ + if (test_thread_flag(TIF_ADDR32)) + current_thread_info()->status |= TS_COMPAT; area->vaddr = get_unmapped_area(NULL, TASK_SIZE - PAGE_SIZE, PAGE_SIZE, 0, 0); + current_thread_info()->status &= ~TS_COMPAT; if (IS_ERR_VALUE(area->vaddr)) { ret = area->vaddr; goto fail;
Quoted 2 lines
Urgh. Also I'm a little bit confused;
set_personality_ia32(), which is what setsTIF_ADDR32(afaict) would also setTS_COMPAT, no?
Yep. But TS_COMPAT is per syscall, not per task. It is cleared in arch_exit_to_user_mode_prepare(), in this case after sys_execve() syscall.
Oleg.
Oleg Nesterov
maintainerQuoted 17 lines · Peter Zijlstra
Quoted 13 lines · Oleg Nesterov
or this one
--- a/arch/x86/kernel/sys_x86_64.c +++ b/arch/x86/kernel/sys_x86_64.c @@ -205,6 +205,8 @@ arch_get_unmapped_area_topdown(struct file *filp, unsigned long addr0, info.low_limit = PAGE_SIZE; info.high_limit = get_mmap_base(0); + if (test_thread_flag(TIF_ADDR32)) + info.high_limit = current->mm->mmap_compat_base; if (!(filp && is_file_hugepages(filp))) { info.start_gap = stack_guard_placement(vm_flags); info.align_offset = pgoff << PAGE_SHIFT;
Quoted 4 lines
This one would be broken if there is ever a case where
get_unmapped_area()is called for mm !=current->mm.Ah, but
get_mmap_base()seems to already rely on that. What a mess :/
Yes, mm_get_unmapped_area_vmflags() paths assume mm == current->mm
Quoted 3 lines
Why can't
get_mmap_base()rely onTIF_ADDR32rather thanTS_COMPAT? What funny games are being played here? Mixed mode mm where some threads are 32bit and others are not?
Exactly. I don't understand this. And again, __get_unmapped_area() will use TIF_ADDR32 in the "if (addr > TASK_SIZE - len)" check and return ENOMEM anyway. I don't see the point in using in_32bit_syscall().
And. I just noticed another problem. What if CONFIG_X86_X32_ABI=y ? In this case ->orig_ax = -1 and this makes in_x32_syscall() and thus in_32bit_syscall() true.
OK, we need a simple fix for stable, I'll make a patch based on TS_COMPAT hack for now.
Oleg.
Oleg Nesterov
maintainerPATCH 1/1 x86/uprobes: Fix XOL allocation failure for 32-bit tasks
In reply to Oleg NesterovThis script
#!/usr/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
echo 'void main(void) {}' > TEST.c
# -fcf-protection to ensure that the 1st endbr32 insn can't be emulated gcc -m32 -fcf-protection=branch TEST.c -o test
bpftrace -e 'uprobe:./test:main {}' -c ./test
"hangs", the probed ./test task enters an endless loop.
The problem is that with randomize_va_space == 0 get_unmapped_area(TASK_SIZE - PAGE_SIZE) called by xol_add_vma() can not just return the "addr == TASK_SIZE - PAGE_SIZE" hint, this addr is used by the stack vma.
arch_get_unmapped_area_topdown() doesn't take TIF_ADDR32 into account and in_32bit_syscall() is false, this leads to info.high_limit > TASK_SIZE. vm_unmapped_area() happily returns the high address > TASK_SIZE and then get_unmapped_area() returns -ENOMEM after the "if (addr > TASK_SIZE - len)" check.
handle_swbp() doesn't report this failure (probably it should) and silently restarts the probed insn. Endless loop.
I think that the right fix should change the x86 get_unmapped_area() paths to rely on TIF_ADDR32 rather than in_32bit_syscall(). Note also that if CONFIG_X86_X32_ABI=y, in_x32_syscall() falsely returns true in this case because ->orig_ax = -1.
But we need a simple fix for -stable, so this patch just sets TS_COMPAT if the probed task is 32-bit to make in_ia32_syscall() true.
--- arch/x86/kernel/uprobes.c | 24 ++++++++++++++++++++++++ include/linux/uprobes.h | 1 + kernel/events/uprobes.c | 10 +++++++--- 3 files changed, 32 insertions(+), 3 deletions(-)
arch/x86/kernel/uprobes.c +24 -0
@@ -1823,3 +1823,27 @@ bool is_uprobe_at_func_entry(struct pt_regs *regs)return false; } + +#ifdef CONFIG_IA32_EMULATION +unsigned long __weak arch_uprobe_get_xol_area (void ) +{ +struct thread_info *ti =current_thread_info (); +unsigned long vaddr; + +/* +* HACK: we are not in a syscall, but x86 get_unmapped_area() paths +* ignore TIF_ADDR32 and rely on in_32bit_syscall() to calculate +* vm_unmapped_area_info.high_limit. +* +* The #ifdef above doesn't cover the CONFIG_X86_X32_ABI=y case, +* but in this case in_32bit_syscall() -> in_x32_syscall() always +* (falsely) returns true because ->orig_ax == -1. +*/ +if (test_thread_flag (TIF_ADDR32 )) + ti->status |=TS_COMPAT ; + vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); + ti->status &= ~TS_COMPAT ; + +return vaddr; +} +#endif
include/linux/uprobes.h +1 -0
@@ -242,6 +242,7 @@ extern void arch_uprobe_clear_state(struct mm_struct *mm);extern void arch_uprobe_init_state (struct mm_struct *mm);extern void handle_syscall_uprobe (struct pt_regs *regs,unsigned long bp_vaddr);extern void arch_uprobe_optimize (struct arch_uprobe *auprobe,unsigned long vaddr); +extern unsigned long arch_uprobe_get_xol_area (void );#else /* !CONFIG_UPROBES */ struct uprobes_state { };
kernel/events/uprobes.c +7 -3
@@ -1694,6 +1694,12 @@ static const struct vm_special_mapping xol_mapping = { .mremap = xol_mremap, }; +unsigned long __weak arch_uprobe_get_xol_area (void ) +{ +/* Try to map as high as possible, this is only a hint. */ +return get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); +} +/* Slot allocation for XOL */ static int xol_add_vma (struct mm_struct *mm,struct xol_area *area) {
@@ -1709,9 +1715,7 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area) }if (!area->vaddr) { -/* Try to map as high as possible, this is only a hint. */ - area->vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE , -PAGE_SIZE ,0 ,0 ); + area->vaddr =arch_uprobe_get_xol_area ();if (IS_ERR_VALUE (area->vaddr)) { ret = area->vaddr; goto fail;
Signature · 1 line
2.52.0
Peter Zijlstra
maintainerRe: [PATCH] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
Quoted 34 lines · Oleg Nesterov
This script
#!/usr/bin/bash
echo 0 >
/proc/sys/kernel/randomize_va_spaceecho 'void main(void) {}' >
TEST.c# -fcf-protection to ensure that the 1st endbr32 insn can't be emulated gcc -m32 -fcf-protection=branch
TEST.c-o testbpftrace -e 'uprobe:./test:main {}' -c ./test
"hangs", the probed ./test task enters an endless loop.
The problem is that with
randomize_va_space== 0get_unmapped_area(TASK_SIZE - PAGE_SIZE)called byxol_add_vma()can not just return the "addr ==TASK_SIZE-PAGE_SIZE" hint, this addr is used by the stack vma.
arch_get_unmapped_area_topdown()doesn't takeTIF_ADDR32into account andin_32bit_syscall()is false, this leads to info.high_limit >TASK_SIZE.vm_unmapped_area()happily returns the high address >TASK_SIZEand thenget_unmapped_area()returns-ENOMEMafter the "if (addr >TASK_SIZE- len)" check.
handle_swbp()doesn't report this failure (probably it should) and silently restarts the probed insn. Endless loop.I think that the right fix should change the x86
get_unmapped_area()paths to rely onTIF_ADDR32rather thanin_32bit_syscall(). Note also that ifCONFIG_X86_X32_ABI=y,in_x32_syscall()falsely returns true in this case because ->orig_ax = -1.
Quoted 7 lines
But we need a simple fix for -stable, so this patch just sets
TS_COMPATif the probed task is 32-bit to makein_ia32_syscall()true.Reported-by Paulo Andrade <[email protected]>Signed-off-by Oleg Nesterov <[email protected]>
Does this want a Fixes tag? Or has this been busted like forever?
Oleg Nesterov
maintainerRe: [PATCH] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
Quoted 11 lines · Peter Zijlstra
Quoted 9 lines · Oleg Nesterov
I think that the right fix should change the x86
get_unmapped_area()paths to rely onTIF_ADDR32rather thanin_32bit_syscall(). Note also that ifCONFIG_X86_X32_ABI=y,in_x32_syscall()falsely returns true in this case because ->orig_ax = -1.But we need a simple fix for -stable, so this patch just sets
TS_COMPATif the probed task is 32-bit to makein_ia32_syscall()true.
Quoted 6 lines
Quoted 4 lines
Reported-by Paulo Andrade <[email protected]>Signed-off-by Oleg Nesterov <[email protected]>Does this want a Fixes tag? Or has this been busted like forever?
All I can say is that this problem is old enough.
I'll try to look into git history tomorrow, not sure I will find the right commit to blame...
At first glance v3.10 (rhel-7) is fine, arch_get_unmapped_area_topdown() sets .high_limit = mm->mmap_base and ->mmap_base is initialized by mmap_base() which returns PAGE_ALIGN(TASK_SIZE - gap - rnd).
But v4.18 (rhel-8) is broken, arch/x86/kernel/sys_x86_64.c uses in_compat_syscall().
Oleg.
Oleg Nesterov
maintainerRe: [PATCH] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
Quoted 25 lines · Oleg Nesterov
Quoted 11 lines · Peter Zijlstra
Quoted 9 lines · Oleg Nesterov
I think that the right fix should change the x86
get_unmapped_area()paths to rely onTIF_ADDR32rather thanin_32bit_syscall(). Note also that ifCONFIG_X86_X32_ABI=y,in_x32_syscall()falsely returns true in this case because ->orig_ax = -1.But we need a simple fix for -stable, so this patch just sets
TS_COMPATif the probed task is 32-bit to makein_ia32_syscall()true.Quoted 6 lines
Quoted 4 lines
Reported-by Paulo Andrade <[email protected]>Signed-off-by Oleg Nesterov <[email protected]>Does this want a Fixes tag? Or has this been busted like forever?
All I can say is that this problem is old enough.
I'll try to look into git history tomorrow, not sure I will find the right commit to blame...
Quoted 6 lines
At first glance v3.10 (rhel-7) is fine,
arch_get_unmapped_area_topdown()sets .high_limit =mm->mmap_baseand ->mmap_base is initialized bymmap_base()which returnsPAGE_ALIGN(TASK_SIZE - gap - rnd).But v4.18 (rhel-8) is broken,
arch/x86/kernel/sys_x86_64.cusesin_compat_syscall().
OK, I think we can blame the commit 1b028f784e8c ("86/mm: Introduce mmap_compat_base() for 32-bit mmap()")
Starting from this commit get_unmapped_area() assumes that it can only be called from a syscall.
I think we need to revisit this logic, but can you take this simple/backportable fix^Whack for -stable?
Oleg.
---
commit 1b028f784e8c341e762c264f70dc0ca1418c8b7a
Author: Dmitry Safonov <[email protected]> Date: Mon Mar 6 17:17:19 2017 +0300
x86/mm: Introduce mmap_compat_base() for 32-bit mmap()
mmap() uses a base address, from which it starts to look for a free space for allocation.
The base address is stored in mm->mmap_base, which is calculated during exec(). The address depends on task's size, set rlimit for stack, ASLR randomization. The base depends on the task size and the number of random bits which are different for 64-bit and 32bit applications.
Due to the fact, that the base address is fixed, its mmap() from a compat (32bit) syscall issued by a 64bit task will return a address which is based on the 64bit base address and does not fit into the 32bit address space (4GB). The returned pointer is truncated to 32bit, which results in an invalid address.
To solve store a seperate compat address base plus a compat legacy address base in mm_struct. These bases are calculated at exec() time and can be used later to address the 32bit compat mmap() issued by 64 bit applications.
As a consequence of this change 32-bit applications issuing a 64-bit syscall (after doing a long jump) will get a 64-bit mapping now. Before this change 32-bit applications always got a 32bit mapping.
[ tglx: Massaged changelog and added a comment ]
arch/Kconfig +7 -0
@@ -700,6 +700,13 @@ config ARCH_MMAP_RND_COMPAT_BITS This value can be changed after boot using the /proc/sys/vm/mmap_rnd_compat_bits tunable +config HAVE_ARCH_COMPAT_MMAP_BASES + bool + help + This allows 64bit applications to invoke 32-bit mmap() syscall + and vice-versa 32-bit applications to call 64-bit mmap(). + Required for applications doing different bitness syscalls. + config HAVE_COPY_THREAD_TLS bool help
arch/x86/Kconfig +1 -0
@@ -106,6 +106,7 @@ config X86 select HAVE_ARCH_KMEMCHECK select HAVE_ARCH_MMAP_RND_BITS if MMU select HAVE_ARCH_MMAP_RND_COMPAT_BITS if MMU && COMPAT + select HAVE_ARCH_COMPAT_MMAP_BASES if MMU && COMPAT select HAVE_ARCH_SECCOMP_FILTER select HAVE_ARCH_TRACEHOOK select HAVE_ARCH_TRANSPARENT_HUGEPAGE
arch/x86/include/asm/elf.h +3 -0
@@ -303,6 +303,9 @@ static inline int mmap_is_ia32(void) test_thread_flag(TIF_ADDR32)); } +extern unsigned long tasksize_32bit (void ); +extern unsigned long tasksize_64bit (void ); +#ifdef CONFIG_X86_32 #define __STACK_RND_MASK (is32bit) (0x7ff)
arch/x86/kernel/sys_x86_64.c +19 -4
@@ -17,6 +17,8 @@#include <linux/uaccess.h> #include <linux/elf.h> +#include <asm/elf.h> +#include <asm/compat.h> #include <asm/ia32.h> #include <asm/syscalls.h>
@@ -98,6 +100,18 @@ SYSCALL_DEFINE6(mmap, unsigned long, addr, unsigned long, len,return error; } +static unsigned long get_mmap_base (int is_legacy) +{ +struct mm_struct *mm = current->mm; + +#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES +if (in_compat_syscall ()) +return is_legacy ? mm->mmap_compat_legacy_base + : mm->mmap_compat_base; +#endif +return is_legacy ? mm->mmap_legacy_base : mm->mmap_base; +} +static void find_start_end (unsigned long flags,unsigned long *begin,unsigned long *end) {
@@ -114,10 +128,11 @@ static void find_start_end(unsigned long flags, unsigned long *begin,if (current->flags &PF_RANDOMIZE ) { *begin =randomize_page (*begin,0x02000000 ); } - }else { - *begin = current->mm->mmap_legacy_base; - *end =TASK_SIZE ; +return ; } + + *begin =get_mmap_base (1 ); + *end =in_compat_syscall () ?tasksize_32bit () :tasksize_64bit (); }unsigned long
@@ -191,7 +206,7 @@ arch_get_unmapped_area_topdown(struct file *filp, const unsigned long addr0, info.flags =VM_UNMAPPED_AREA_TOPDOWN ; info.length = len; info.low_limit =PAGE_SIZE ; - info.high_limit = mm->mmap_base; + info.high_limit =get_mmap_base (0 ); info.align_mask =0 ; info.align_offset = pgoff <<PAGE_SHIFT ;if (filp) {
arch/x86/mm/mmap.c +34 -13
73 lines not on this page · raw message
include/linux/mm_types.h +5 -0
@@ -367,6 +367,11 @@ struct mm_struct {#endif unsigned long mmap_base;/* base of mmap area */ unsigned long mmap_legacy_base;/* base of mmap area in bottom-up allocations */ +#ifdef CONFIG_HAVE_ARCH_COMPAT_MMAP_BASES +/* Base adresses for compatible mmap() */ +unsigned long mmap_compat_base; +unsigned long mmap_compat_legacy_base; +#endif unsigned long task_size;/* size of task vm space */ unsigned long highest_vm_end;/* highest vma end address */ pgd_t * pgd;
[tip: perf/core] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: 69044a0cbfdcc6e788c8a1f8e050d108038461d6 Gitweb: https://git.kernel.org/tip/69044a0cbfdcc6e788c8a1f8e050d108038461d6 Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 11 Jan 2026 16:00:37 +01:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Thu, 15 Jan 2026 10:04:28 +01:00
x86/uprobes: Fix XOL allocation failure for 32-bit tasks
This script
#!/usr/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
echo 'void main(void) {}' > TEST.c
# -fcf-protection to ensure that the 1st endbr32 insn can't be emulated gcc -m32 -fcf-protection=branch TEST.c -o test
bpftrace -e 'uprobe:./test:main {}' -c ./test
"hangs", the probed ./test task enters an endless loop.
The problem is that with randomize_va_space == 0 get_unmapped_area(TASK_SIZE - PAGE_SIZE) called by xol_add_vma() can not just return the "addr == TASK_SIZE - PAGE_SIZE" hint, this addr is used by the stack vma.
arch_get_unmapped_area_topdown() doesn't take TIF_ADDR32 into account and in_32bit_syscall() is false, this leads to info.high_limit > TASK_SIZE. vm_unmapped_area() happily returns the high address > TASK_SIZE and then get_unmapped_area() returns -ENOMEM after the "if (addr > TASK_SIZE - len)" check.
handle_swbp() doesn't report this failure (probably it should) and silently restarts the probed insn. Endless loop.
I think that the right fix should change the x86 get_unmapped_area() paths to rely on TIF_ADDR32 rather than in_32bit_syscall(). Note also that if CONFIG_X86_X32_ABI=y, in_x32_syscall() falsely returns true in this case because ->orig_ax = -1.
But we need a simple fix for -stable, so this patch just sets TS_COMPAT if the probed task is 32-bit to make in_ia32_syscall() true.
--- arch/x86/kernel/uprobes.c | 24 ++++++++++++++++++++++++ include/linux/uprobes.h | 1 + kernel/events/uprobes.c | 10 +++++++--- 3 files changed, 32 insertions(+), 3 deletions(-)
arch/x86/kernel/uprobes.c +24 -0
@@ -1823,3 +1823,27 @@ bool is_uprobe_at_func_entry(struct pt_regs *regs)return false; } + +#ifdef CONFIG_IA32_EMULATION +unsigned long __weak arch_uprobe_get_xol_area (void ) +{ +struct thread_info *ti =current_thread_info (); +unsigned long vaddr; + +/* +* HACK: we are not in a syscall, but x86 get_unmapped_area() paths +* ignore TIF_ADDR32 and rely on in_32bit_syscall() to calculate +* vm_unmapped_area_info.high_limit. +* +* The #ifdef above doesn't cover the CONFIG_X86_X32_ABI=y case, +* but in this case in_32bit_syscall() -> in_x32_syscall() always +* (falsely) returns true because ->orig_ax == -1. +*/ +if (test_thread_flag (TIF_ADDR32 )) + ti->status |=TS_COMPAT ; + vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); + ti->status &= ~TS_COMPAT ; + +return vaddr; +} +#endif
include/linux/uprobes.h +1 -0
@@ -242,6 +242,7 @@ extern void arch_uprobe_clear_state(struct mm_struct *mm);extern void arch_uprobe_init_state (struct mm_struct *mm);extern void handle_syscall_uprobe (struct pt_regs *regs,unsigned long bp_vaddr);extern void arch_uprobe_optimize (struct arch_uprobe *auprobe,unsigned long vaddr); +extern unsigned long arch_uprobe_get_xol_area (void );#else /* !CONFIG_UPROBES */ struct uprobes_state { };
kernel/events/uprobes.c +7 -3
@@ -1694,6 +1694,12 @@ static const struct vm_special_mapping xol_mapping = { .mremap = xol_mremap, }; +unsigned long __weak arch_uprobe_get_xol_area (void ) +{ +/* Try to map as high as possible, this is only a hint. */ +return get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); +} +/* Slot allocation for XOL */ static int xol_add_vma (struct mm_struct *mm,struct xol_area *area) {
@@ -1709,9 +1715,7 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area) }if (!area->vaddr) { -/* Try to map as high as possible, this is only a hint. */ - area->vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE , -PAGE_SIZE ,0 ,0 ); + area->vaddr =arch_uprobe_get_xol_area ();if (IS_ERR_VALUE (area->vaddr)) { ret = area->vaddr; goto fail;
Oleg Nesterov
maintainerRe: [tip: perf/core] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
Quoted 2 lines · On 01/15, tip-bot2 for Oleg Nesterov wrote:
The following commit has been merged into the perf/core branch of tip:
Damn!
thanks Peter, but...
arch/x86/kernel/uprobes.c bool is_uprobe_at_func_entry(struct pt_regs *regs) · line 1828
Quoted 9 lines
arch/x86/kernel/uprobes.c +3
-0@@ -1823,3 +1823,27 @@ bool is_uprobe_at_func_entry(struct pt_regs *regs)return false; } + +#ifdef CONFIG_IA32_EMULATION +unsigned long __weak arch_uprobe_get_xol_area (void )
Oh, but x86 version should not be __weak. Copy-and-paste error, sorry :/
What do you want me to do now,
- send V2 ?
- send another s/__weak// patch on top of this hack?
- or perhaps you can fix this yourself?
Oleg.
Peter Zijlstra
maintainerRe: [tip: perf/core] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
Quoted 20 lines · Oleg Nesterov
Quoted 2 lines · On 01/15, tip-bot2 for Oleg Nesterov wrote:
The following commit has been merged into the perf/core branch of tip:
Damn!
thanks Peter, but...
arch/x86/kernel/uprobes.c bool is_uprobe_at_func_entry(struct pt_regs *regs) · line 1828
Quoted 9 lines
arch/x86/kernel/uprobes.c +3
-0@@ -1823,3 +1823,27 @@ bool is_uprobe_at_func_entry(struct pt_regs *regs)return false; } + +#ifdef CONFIG_IA32_EMULATION +unsigned long __weak arch_uprobe_get_xol_area (void )Oh, but x86 version should not be
__weak. Copy-and-paste error, sorry :/
Quoted 7 lines
What do you want me to do now,
- send V2 ?
- send another s/__weak// patch on top of this hack?
- or perhaps you can fix this yourself?
I'll have to rebase anyway, might as well fix it directly.
Thanks!
[tip: perf/core] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: 40bb50531482258bf410d84fa4ecb944e02b887b Gitweb: https://git.kernel.org/tip/40bb50531482258bf410d84fa4ecb944e02b887b Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 11 Jan 2026 16:00:37 +01:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Fri, 16 Jan 2026 11:46:25 +01:00
x86/uprobes: Fix XOL allocation failure for 32-bit tasks
This script
#!/usr/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
echo 'void main(void) {}' > TEST.c
# -fcf-protection to ensure that the 1st endbr32 insn can't be emulated gcc -m32 -fcf-protection=branch TEST.c -o test
bpftrace -e 'uprobe:./test:main {}' -c ./test
"hangs", the probed ./test task enters an endless loop.
The problem is that with randomize_va_space == 0 get_unmapped_area(TASK_SIZE - PAGE_SIZE) called by xol_add_vma() can not just return the "addr == TASK_SIZE - PAGE_SIZE" hint, this addr is used by the stack vma.
arch_get_unmapped_area_topdown() doesn't take TIF_ADDR32 into account and in_32bit_syscall() is false, this leads to info.high_limit > TASK_SIZE. vm_unmapped_area() happily returns the high address > TASK_SIZE and then get_unmapped_area() returns -ENOMEM after the "if (addr > TASK_SIZE - len)" check.
handle_swbp() doesn't report this failure (probably it should) and silently restarts the probed insn. Endless loop.
I think that the right fix should change the x86 get_unmapped_area() paths to rely on TIF_ADDR32 rather than in_32bit_syscall(). Note also that if CONFIG_X86_X32_ABI=y, in_x32_syscall() falsely returns true in this case because ->orig_ax = -1.
But we need a simple fix for -stable, so this patch just sets TS_COMPAT if the probed task is 32-bit to make in_ia32_syscall() true.
--- arch/x86/kernel/uprobes.c | 24 ++++++++++++++++++++++++ include/linux/uprobes.h | 1 + kernel/events/uprobes.c | 10 +++++++--- 3 files changed, 32 insertions(+), 3 deletions(-)
arch/x86/kernel/uprobes.c +24 -0
@@ -1823,3 +1823,27 @@ bool is_uprobe_at_func_entry(struct pt_regs *regs)return false; } + +#ifdef CONFIG_IA32_EMULATION +unsigned long arch_uprobe_get_xol_area (void ) +{ +struct thread_info *ti =current_thread_info (); +unsigned long vaddr; + +/* +* HACK: we are not in a syscall, but x86 get_unmapped_area() paths +* ignore TIF_ADDR32 and rely on in_32bit_syscall() to calculate +* vm_unmapped_area_info.high_limit. +* +* The #ifdef above doesn't cover the CONFIG_X86_X32_ABI=y case, +* but in this case in_32bit_syscall() -> in_x32_syscall() always +* (falsely) returns true because ->orig_ax == -1. +*/ +if (test_thread_flag (TIF_ADDR32 )) + ti->status |=TS_COMPAT ; + vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); + ti->status &= ~TS_COMPAT ; + +return vaddr; +} +#endif
include/linux/uprobes.h +1 -0
@@ -242,6 +242,7 @@ extern void arch_uprobe_clear_state(struct mm_struct *mm);extern void arch_uprobe_init_state (struct mm_struct *mm);extern void handle_syscall_uprobe (struct pt_regs *regs,unsigned long bp_vaddr);extern void arch_uprobe_optimize (struct arch_uprobe *auprobe,unsigned long vaddr); +extern unsigned long arch_uprobe_get_xol_area (void );#else /* !CONFIG_UPROBES */ struct uprobes_state { };
kernel/events/uprobes.c +7 -3
@@ -1694,6 +1694,12 @@ static const struct vm_special_mapping xol_mapping = { .mremap = xol_mremap, }; +unsigned long __weak arch_uprobe_get_xol_area (void ) +{ +/* Try to map as high as possible, this is only a hint. */ +return get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); +} +/* Slot allocation for XOL */ static int xol_add_vma (struct mm_struct *mm,struct xol_area *area) {
@@ -1709,9 +1715,7 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area) }if (!area->vaddr) { -/* Try to map as high as possible, this is only a hint. */ - area->vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE , -PAGE_SIZE ,0 ,0 ); + area->vaddr =arch_uprobe_get_xol_area ();if (IS_ERR_VALUE (area->vaddr)) { ret = area->vaddr; goto fail;
[tip: perf/core] x86/uprobes: Fix XOL allocation failure for 32-bit tasks
In reply to Oleg NesterovThe following commit has been merged into the perf/core branch of tip:
The following commit has been merged into the perf/core branch of tip:
Commit-ID: d55c571e4333fac71826e8db3b9753fadfbead6a Gitweb: https://git.kernel.org/tip/d55c571e4333fac71826e8db3b9753fadfbead6a Author: Oleg Nesterov <[email protected]> AuthorDate: Sun, 11 Jan 2026 16:00:37 +01:00 Committer: Peter Zijlstra <[email protected]> CommitterDate: Fri, 16 Jan 2026 16:23:54 +01:00
x86/uprobes: Fix XOL allocation failure for 32-bit tasks
This script
#!/usr/bin/bash
echo 0 > /proc/sys/kernel/randomize_va_space
echo 'void main(void) {}' > TEST.c
# -fcf-protection to ensure that the 1st endbr32 insn can't be emulated gcc -m32 -fcf-protection=branch TEST.c -o test
bpftrace -e 'uprobe:./test:main {}' -c ./test
"hangs", the probed ./test task enters an endless loop.
The problem is that with randomize_va_space == 0 get_unmapped_area(TASK_SIZE - PAGE_SIZE) called by xol_add_vma() can not just return the "addr == TASK_SIZE - PAGE_SIZE" hint, this addr is used by the stack vma.
arch_get_unmapped_area_topdown() doesn't take TIF_ADDR32 into account and in_32bit_syscall() is false, this leads to info.high_limit > TASK_SIZE. vm_unmapped_area() happily returns the high address > TASK_SIZE and then get_unmapped_area() returns -ENOMEM after the "if (addr > TASK_SIZE - len)" check.
handle_swbp() doesn't report this failure (probably it should) and silently restarts the probed insn. Endless loop.
I think that the right fix should change the x86 get_unmapped_area() paths to rely on TIF_ADDR32 rather than in_32bit_syscall(). Note also that if CONFIG_X86_X32_ABI=y, in_x32_syscall() falsely returns true in this case because ->orig_ax = -1.
But we need a simple fix for -stable, so this patch just sets TS_COMPAT if the probed task is 32-bit to make in_ia32_syscall() true.
--- arch/x86/kernel/uprobes.c | 24 ++++++++++++++++++++++++ include/linux/uprobes.h | 1 + kernel/events/uprobes.c | 10 +++++++--- 3 files changed, 32 insertions(+), 3 deletions(-)
arch/x86/kernel/uprobes.c +24 -0
@@ -1823,3 +1823,27 @@ bool is_uprobe_at_func_entry(struct pt_regs *regs)return false; } + +#ifdef CONFIG_IA32_EMULATION +unsigned long arch_uprobe_get_xol_area (void ) +{ +struct thread_info *ti =current_thread_info (); +unsigned long vaddr; + +/* +* HACK: we are not in a syscall, but x86 get_unmapped_area() paths +* ignore TIF_ADDR32 and rely on in_32bit_syscall() to calculate +* vm_unmapped_area_info.high_limit. +* +* The #ifdef above doesn't cover the CONFIG_X86_X32_ABI=y case, +* but in this case in_32bit_syscall() -> in_x32_syscall() always +* (falsely) returns true because ->orig_ax == -1. +*/ +if (test_thread_flag (TIF_ADDR32 )) + ti->status |=TS_COMPAT ; + vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); + ti->status &= ~TS_COMPAT ; + +return vaddr; +} +#endif
include/linux/uprobes.h +1 -0
@@ -242,6 +242,7 @@ extern void arch_uprobe_clear_state(struct mm_struct *mm);extern void arch_uprobe_init_state (struct mm_struct *mm);extern void handle_syscall_uprobe (struct pt_regs *regs,unsigned long bp_vaddr);extern void arch_uprobe_optimize (struct arch_uprobe *auprobe,unsigned long vaddr); +extern unsigned long arch_uprobe_get_xol_area (void );#else /* !CONFIG_UPROBES */ struct uprobes_state { };
kernel/events/uprobes.c +7 -3
@@ -1694,6 +1694,12 @@ static const struct vm_special_mapping xol_mapping = { .mremap = xol_mremap, }; +unsigned long __weak arch_uprobe_get_xol_area (void ) +{ +/* Try to map as high as possible, this is only a hint. */ +return get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE ,PAGE_SIZE ,0 ,0 ); +} +/* Slot allocation for XOL */ static int xol_add_vma (struct mm_struct *mm,struct xol_area *area) {
@@ -1709,9 +1715,7 @@ static int xol_add_vma(struct mm_struct *mm, struct xol_area *area) }if (!area->vaddr) { -/* Try to map as high as possible, this is only a hint. */ - area->vaddr =get_unmapped_area (NULL ,TASK_SIZE -PAGE_SIZE , -PAGE_SIZE ,0 ,0 ); + area->vaddr =arch_uprobe_get_xol_area ();if (IS_ERR_VALUE (area->vaddr)) { ret = area->vaddr; goto fail;