From 15ad98536ad9410fb32ddf1ff09389b677643faa Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Mon, 17 Jul 2023 08:37:17 +0200 Subject: [PATCH 1/5] linux-user: Fix qemu brk() to not zero bytes on current page The qemu brk() implementation is too aggressive and cleans remaining bytes on the current page above the last brk address. But some existing applications are buggy and read/write bytes above their current heap address. On a phyiscal machine this does not trigger a runtime error as long as the access happens on the same page. Additionally the Linux kernel allocates only full pages and does no zeroing on already allocated pages, even if the brk address is lowered. Fix qemu to behave the same way as the kernel does. Do not touch already allocated pages, and - when running with different page sizes of guest and host - zero out only those memory areas where the host page size is bigger than the guest page size. Signed-off-by: Helge Deller Tested-by: "Markus F.X.J. Oberhumer" Fixes: 86f04735ac ("linux-user: Fix brk() to release pages") Cc: qemu-stable@nongnu.org Buglink: https://github.com/upx/upx/issues/683 --- linux-user/syscall.c | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index c99ef9c01e..ee54eed33b 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -829,10 +829,8 @@ abi_long do_brk(abi_ulong brk_val) /* brk_val and old target_brk might be on the same page */ if (new_brk == TARGET_PAGE_ALIGN(target_brk)) { - if (brk_val > target_brk) { - /* empty remaining bytes in (possibly larger) host page */ - memset(g2h_untagged(target_brk), 0, new_host_brk_page - target_brk); - } + /* empty remaining bytes in (possibly larger) host page */ + memset(g2h_untagged(new_brk), 0, new_host_brk_page - new_brk); target_brk = brk_val; return target_brk; } @@ -840,7 +838,7 @@ abi_long do_brk(abi_ulong brk_val) /* Release heap if necesary */ if (new_brk < target_brk) { /* empty remaining bytes in (possibly larger) host page */ - memset(g2h_untagged(brk_val), 0, new_host_brk_page - brk_val); + memset(g2h_untagged(new_brk), 0, new_host_brk_page - new_brk); /* free unused host pages and set new brk_page */ target_munmap(new_host_brk_page, brk_page - new_host_brk_page); @@ -873,7 +871,7 @@ abi_long do_brk(abi_ulong brk_val) * come from the remaining part of the previous page: it may * contains garbage data due to a previous heap usage (grown * then shrunken). */ - memset(g2h_untagged(target_brk), 0, brk_page - target_brk); + memset(g2h_untagged(brk_page), 0, HOST_PAGE_ALIGN(brk_page) - brk_page); target_brk = brk_val; brk_page = new_host_brk_page; From dfe49864afb06e7e452a4366051697bc4fcfc1a5 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Mon, 17 Jul 2023 12:27:13 +0200 Subject: [PATCH 2/5] linux-user: Prohibit brk() to to shrink below initial heap address Since commit 86f04735ac ("linux-user: Fix brk() to release pages") it's possible for userspace applications to reduce their memory footprint by calling brk() with a lower address and free up memory. Before that commit guest heap memory was never unmapped. But the Linux kernel prohibits to reduce brk() below the initial memory address which is set at startup by the set_brk() function in binfmt_elf.c. Such a range check was missed in commit 86f04735ac. This patch adds the missing check by storing the initial brk value in initial_target_brk and verify any new brk addresses against that value. Tested with the i386 upx binary from https://github.com/upx/upx/releases/download/v4.0.2/upx-4.0.2-i386_linux.tar.xz Signed-off-by: Helge Deller Tested-by: "Markus F.X.J. Oberhumer" Fixes: 86f04735ac ("linux-user: Fix brk() to release pages") Cc: qemu-stable@nongnu.org Buglink: https://github.com/upx/upx/issues/683 --- linux-user/syscall.c | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index ee54eed33b..125fcbe423 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -801,12 +801,13 @@ static inline int host_to_target_sock_type(int host_type) return target_type; } -static abi_ulong target_brk; +static abi_ulong target_brk, initial_target_brk; static abi_ulong brk_page; void target_set_brk(abi_ulong new_brk) { target_brk = TARGET_PAGE_ALIGN(new_brk); + initial_target_brk = target_brk; brk_page = HOST_PAGE_ALIGN(target_brk); } @@ -824,6 +825,11 @@ abi_long do_brk(abi_ulong brk_val) return target_brk; } + /* do not allow to shrink below initial brk value */ + if (brk_val < initial_target_brk) { + brk_val = initial_target_brk; + } + new_brk = TARGET_PAGE_ALIGN(brk_val); new_host_brk_page = HOST_PAGE_ALIGN(brk_val); From eac78a4b0b7da4de2c0a297f4d528ca9cc6256a3 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Mon, 17 Jul 2023 12:39:38 +0200 Subject: [PATCH 3/5] linux-user: Fix signed math overflow in brk() syscall MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fix the math overflow when calculating the new_malloc_size. new_host_brk_page and brk_page are unsigned integers. If userspace reduces the heap, new_host_brk_page is lower than brk_page which results in a huge positive number (but should actually be negative). Fix it by adding a proper check and as such make the code more readable. Signed-off-by: Helge Deller Tested-by: "Markus F.X.J. Oberhumer" Reviewed-by: Philippe Mathieu-Daudé Fixes: 86f04735ac ("linux-user: Fix brk() to release pages") Cc: qemu-stable@nongnu.org Buglink: https://github.com/upx/upx/issues/683 --- linux-user/syscall.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/linux-user/syscall.c b/linux-user/syscall.c index 125fcbe423..95727a816a 100644 --- a/linux-user/syscall.c +++ b/linux-user/syscall.c @@ -860,12 +860,13 @@ abi_long do_brk(abi_ulong brk_val) * itself); instead we treat "mapped but at wrong address" as * a failure and unmap again. */ - new_alloc_size = new_host_brk_page - brk_page; - if (new_alloc_size) { + if (new_host_brk_page > brk_page) { + new_alloc_size = new_host_brk_page - brk_page; mapped_addr = get_errno(target_mmap(brk_page, new_alloc_size, PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0)); } else { + new_alloc_size = 0; mapped_addr = brk_page; } From d971040c2d16b7fda9fcd52c993262b437501538 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Mon, 17 Jul 2023 19:06:32 +0200 Subject: [PATCH 4/5] linux-user: Fix strace output for old_mmap The old_mmap syscall (e.g. on i386) hands over the parameters in a struct. Adjust the strace output to print the correct values. Signed-off-by: Helge Deller Reported-by: John Reiser Closes: https://gitlab.com/qemu-project/qemu/-/issues/1760 --- linux-user/strace.c | 47 ++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 44 insertions(+), 3 deletions(-) diff --git a/linux-user/strace.c b/linux-user/strace.c index bbd29148d4..e0ab8046ec 100644 --- a/linux-user/strace.c +++ b/linux-user/strace.c @@ -3767,10 +3767,24 @@ print_utimensat(CPUArchState *cpu_env, const struct syscallname *name, #if defined(TARGET_NR_mmap) || defined(TARGET_NR_mmap2) static void -print_mmap(CPUArchState *cpu_env, const struct syscallname *name, +print_mmap_both(CPUArchState *cpu_env, const struct syscallname *name, abi_long arg0, abi_long arg1, abi_long arg2, - abi_long arg3, abi_long arg4, abi_long arg5) + abi_long arg3, abi_long arg4, abi_long arg5, + bool is_old_mmap) { + if (is_old_mmap) { + abi_ulong *v; + abi_ulong argp = arg0; + if (!(v = lock_user(VERIFY_READ, argp, 6 * sizeof(abi_ulong), 1))) + return; + arg0 = tswapal(v[0]); + arg1 = tswapal(v[1]); + arg2 = tswapal(v[2]); + arg3 = tswapal(v[3]); + arg4 = tswapal(v[4]); + arg5 = tswapal(v[5]); + unlock_user(v, argp, 0); + } print_syscall_prologue(name); print_pointer(arg0, 0); print_raw_param("%d", arg1, 0); @@ -3780,7 +3794,34 @@ print_mmap(CPUArchState *cpu_env, const struct syscallname *name, print_raw_param("%#x", arg5, 1); print_syscall_epilogue(name); } -#define print_mmap2 print_mmap +#endif + +#if defined(TARGET_NR_mmap) +static void +print_mmap(CPUArchState *cpu_env, const struct syscallname *name, + abi_long arg0, abi_long arg1, abi_long arg2, + abi_long arg3, abi_long arg4, abi_long arg5) +{ + return print_mmap_both(cpu_env, name, arg0, arg1, arg2, arg3, + arg4, arg5, +#if defined(TARGET_NR_mmap2) + true +#else + false +#endif + ); +} +#endif + +#if defined(TARGET_NR_mmap2) +static void +print_mmap2(CPUArchState *cpu_env, const struct syscallname *name, + abi_long arg0, abi_long arg1, abi_long arg2, + abi_long arg3, abi_long arg4, abi_long arg5) +{ + return print_mmap_both(cpu_env, name, arg0, arg1, arg2, arg3, + arg4, arg5, false); +} #endif #ifdef TARGET_NR_mprotect From 518f32221af759a29500ac172c4c857bef142067 Mon Sep 17 00:00:00 2001 From: Helge Deller Date: Mon, 17 Jul 2023 22:06:02 +0200 Subject: [PATCH 5/5] linux-user: Fix qemu-arm to run static armhf binaries qemu-user crashes immediately when running static binaries on the armhf architecture. The problem is the memory layout where the executable is loaded before the interpreter library, in which case the reserved brk region clashes with the interpreter code and is released before qemu tries to start the program. At load time qemu calculates a brk value for interpreter and executable each. The fix is to choose the higher one of both. Signed-off-by: Helge Deller Cc: Andreas Schwab Cc: qemu-stable@nongnu.org Reported-by: Venkata.Pyla@toshiba-tsip.com Closes: https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1040981 --- linux-user/elfload.c | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/linux-user/elfload.c b/linux-user/elfload.c index a26200d9f3..94951630b1 100644 --- a/linux-user/elfload.c +++ b/linux-user/elfload.c @@ -3615,6 +3615,13 @@ int load_elf_binary(struct linux_binprm *bprm, struct image_info *info) if (elf_interpreter) { load_elf_interp(elf_interpreter, &interp_info, bprm->buf); + /* + * adjust brk address if the interpreter was loaded above the main + * executable, e.g. happens with static binaries on armhf + */ + if (interp_info.brk > info->brk) { + info->brk = interp_info.brk; + } /* If the program interpreter is one of these two, then assume an iBCS2 image. Otherwise assume a native linux image. */