common-user/probe-guest-base.c | 9 +++++++-- include/user/probe-guest-base.h | 3 +++ 2 files changed, 10 insertions(+), 2 deletions(-)
A non-PIE qemu is loaded below 4 GiB. That includes every static
build on FreeBSD and Linux builds configured with --disable-pie.
With the identity map (guest_base 0), a non-PIE guest program can
be mapped over qemu.
Only use the identity map when qemu is loaded at 4 GiB or higher.
Non-PIE programs are normally linked below 4 GiB, and PIE programs
are loaded at or above it (ppc64 Linux loads them at exactly 4 GiB).
Signed-off-by: Rick Richard <rick@sloservers.com>
---
On FreeBSD a static qemu can't be PIE, so bsd-user is always loaded
at 0x200000. pgb_dynamic() picks guest_base 0 because the initial
image doesn't overlap qemu, but rtld later maps a non-PIE executable
at its link address on top of qemu's own text.
This isn't BSD specific, I was able to reproduce on Linux as well by
disabling PIE.
I tested the fix on Linux with static-pie and --disable-pie builds,
and on FreeBSD with aarch64, riscv64 and x86_64 guests. When qemu is
loaded high it still uses the identity map. When it's loaded low it
picks a non-zero guest_base and the guest runs normally.
I couldn't find an existing way in qemu to get its own load address,
so I used __executable_start, which both GNU ld and lld provide. If
there's a better way I'm happy to change it.
common-user/probe-guest-base.c | 9 +++++++--
include/user/probe-guest-base.h | 3 +++
2 files changed, 10 insertions(+), 2 deletions(-)
diff --git a/common-user/probe-guest-base.c b/common-user/probe-guest-base.c
index 6f1247cf..226fe7d4 100644
--- a/common-user/probe-guest-base.c
+++ b/common-user/probe-guest-base.c
@@ -269,8 +269,13 @@ static void pgb_dynamic(const char *image_name, const PGBRange *image_range,
uintptr_t brk, ret;
PGBAddrs ga;
- /* Try the identity map first. */
- if (pgb_addr_set(&ga, image_range, commpage_range, true)) {
+ /*
+ * Try the identity map first, but only if qemu is loaded high.
+ * A non-PIE qemu loads low, where a guest's non-PIE executable
+ * could be mapped over it.
+ */
+ if ((uintptr_t)__executable_start >= 4 * GiB &&
+ pgb_addr_set(&ga, image_range, commpage_range, true)) {
brk = (uintptr_t)sbrk(0);
if (pgb_try_mmap_set(&ga, 0, brk)) {
guest_base = 0;
diff --git a/include/user/probe-guest-base.h b/include/user/probe-guest-base.h
index d7492ef4..fe7f971f 100644
--- a/include/user/probe-guest-base.h
+++ b/include/user/probe-guest-base.h
@@ -9,6 +9,9 @@
#include "exec/vaddr.h"
+/* Defined by the linker: the start address of qemu's own executable image. */
+extern char __executable_start[];
+
typedef struct PGBRange {
vaddr lo;
vaddr hi;
On 10/2/26 12:52, Rick Richard wrote: > A non-PIE qemu is loaded below 4 GiB. That includes every static > build on FreeBSD and Linux builds configured with --disable-pie. > With the identity map (guest_base 0), a non-PIE guest program can > be mapped over qemu. > > Only use the identity map when qemu is loaded at 4 GiB or higher. > Non-PIE programs are normally linked below 4 GiB, and PIE programs > are loaded at or above it (ppc64 Linux loads them at exactly 4 GiB). > > Signed-off-by: Rick Richard <rick@sloservers.com> > --- > On FreeBSD a static qemu can't be PIE, so bsd-user is always loaded > at 0x200000. pgb_dynamic() picks guest_base 0 because the initial > image doesn't overlap qemu, but rtld later maps a non-PIE executable > at its link address on top of qemu's own text. > > This isn't BSD specific, I was able to reproduce on Linux as well by > disabling PIE. > > I tested the fix on Linux with static-pie and --disable-pie builds, > and on FreeBSD with aarch64, riscv64 and x86_64 guests. When qemu is > loaded high it still uses the identity map. When it's loaded low it > picks a non-zero guest_base and the guest runs normally. > > I couldn't find an existing way in qemu to get its own load address, > so I used __executable_start, which both GNU ld and lld provide. If > there's a better way I'm happy to change it. There should be no need for this at all. The probe sequence should notice conflicting addresses and adjust. What's your failure case? r~
On 10/10/2026 1:50 AM, Richard Henderson wrote: > On 10/2/26 12:52, Rick Richard wrote: >> A non-PIE qemu is loaded below 4 GiB. That includes every static >> build on FreeBSD and Linux builds configured with --disable-pie. >> With the identity map (guest_base 0), a non-PIE guest program can >> be mapped over qemu. >> >> Only use the identity map when qemu is loaded at 4 GiB or higher. >> Non-PIE programs are normally linked below 4 GiB, and PIE programs >> are loaded at or above it (ppc64 Linux loads them at exactly 4 GiB). >> >> Signed-off-by: Rick Richard <rick@sloservers.com> >> --- >> On FreeBSD a static qemu can't be PIE, so bsd-user is always loaded >> at 0x200000. pgb_dynamic() picks guest_base 0 because the initial >> image doesn't overlap qemu, but rtld later maps a non-PIE executable >> at its link address on top of qemu's own text. >> >> This isn't BSD specific, I was able to reproduce on Linux as well by >> disabling PIE. >> >> I tested the fix on Linux with static-pie and --disable-pie builds, >> and on FreeBSD with aarch64, riscv64 and x86_64 guests. When qemu is >> loaded high it still uses the identity map. When it's loaded low it >> picks a non-zero guest_base and the guest runs normally. >> >> I couldn't find an existing way in qemu to get its own load address, >> so I used __executable_start, which both GNU ld and lld provide. If >> there's a better way I'm happy to change it. > > There should be no need for this at all. The probe sequence should notice conflicting addresses and adjust. > > What's your failure case? > > > r~ You're right about that, but there's a less obvious case that falls through the cracks: programs loaded through ld.so. qemu only loads ld.so, so that's all the probe sees, and it picks guest_base 0. Then ld.so maps the real program at its fixed address, right on top of qemu. An easy way to hit it is ldd on FreeBSD. In an aarch64 jail with the stock perl package: # ldd /usr/local/bin/perl /usr/local/bin/perl: ld-elf.so.1: /usr/local/bin/perl: mmap of entire address space failed: Cannot allocate memory Same for most other programs in FreeBSD packages. FreeBSD can't link static PIE, so qemu-*-static is always linked at 0x200000. Ports aren't built PIE unless WITH_PIE is set, and non-PIE programs on FreeBSD are linked at 0x200000 by default, the same address as qemu. FreeBSD's ldd runs a program as "ld-elf.so.1 -- prog", so ld.so is all qemu ever loads. The ports QA check runs ldd on every staged binary, and gobject-introspection runs it during its build, which is where I first ran into this. Linux distros ship static-pie, so it mostly hits FreeBSD, but a static --disable-pie build fails the same way on Linux too.
On 10/11/26 08:10, Rick Richard wrote: >> There should be no need for this at all. The probe sequence should notice conflicting addresses and adjust. >> >> What's your failure case? >> >> >> r~ > > You're right about that, but there's a less obvious case that falls > through the cracks: programs loaded through ld.so. qemu only loads > ld.so, so that's all the probe sees, and it picks guest_base 0. Then > ld.so maps the real program at its fixed address, right on top of qemu. > > An easy way to hit it is ldd on FreeBSD. In an aarch64 jail with the > stock perl package: > > # ldd /usr/local/bin/perl > /usr/local/bin/perl: > ld-elf.so.1: /usr/local/bin/perl: mmap of entire address space failed: Cannot allocate memory Mm, right. > FreeBSD can't link static PIE, so qemu-*-static is always linked at > 0x200000. That's unfortunate. We could explicitly link somewhere high in the address space, perhaps. E.g. -Wl,--image-base,0x80000000000 ? r~
On 10/13/2026 7:53 AM, Richard Henderson wrote: > >> FreeBSD can't link static PIE, so qemu-*-static is always linked at >> 0x200000. > > That's unfortunate. We could explicitly link somewhere high in the address space, perhaps. E.g. > > -Wl,--image-base,0x80000000000 > I tried that, but on FreeBSD amd64 it won't link above 2G. The static libc and crtbeginT.o aren't PIC, so they're full of R_X86_64_32 and R_X86_64_32S relocations: ld: error: /usr/lib/crtbeginT.o:(function __do_global_dtors_aux: .text+0x8): relocation R_X86_64_32 out of range: 8796093721768 is not in [0, 4294967295] ld: error: /usr/lib/libc.a(libc_start1.o):(function __libc_start1: .text+0xb7): relocation R_X86_64_32S out of range: 2415920304 is not in [-2147483648, 2147483647] 0x70000000 works though. I linked qemu-aarch64 there with no other changes, and guest_base stays 0. ldd on perl and the other packaged programs I tried work fine with this. It's not really safe though, it just moves qemu out of the way. A guest MAP_FIXED can still land on qemu's memory and crash it, even with a non-zero guest_base, since the heap and code buffer are still in reach. Maybe target_mmap should refuse that and give the guest an error instead? If you want to go with the link address, I can add it to qemu's meson.build for static non-PIE builds.
On 10/15/26 07:04, Rick Richard wrote: > It's not really safe though, it just moves qemu out of the way. A > guest MAP_FIXED can still land on qemu's memory and crash it, even > with a non-zero guest_base, since the heap and code buffer are still > in reach. Maybe target_mmap should refuse that and give the guest an > error instead? The same is true for Linux PIE, just moving the binary into high memory. We don't have a solution for keeping host and guest memory addresses separate. Since we do not control allocation within the host address space (malloc, pthread stacks, TLS, etc), having target_mmap do anything at all would require repeated probing of the host address space on every such allocation, which would be ... well, not impossible, but slow and ungainly. At some point I'd rather just have softmmu for user-only, which solves other emulation problems as well. But that's a lot of work too, and a known overhead of 30-40%. The overhead I'm willing to ignore if it gets us better correctness. r~
On 10/13/26 16:53, Richard Henderson wrote: > On 10/11/26 08:10, Rick Richard wrote: >>> There should be no need for this at all. The probe sequence should notice conflicting addresses and adjust. >>> >>> What's your failure case? >>> >>> >>> r~ >> >> You're right about that, but there's a less obvious case that falls >> through the cracks: programs loaded through ld.so. qemu only loads >> ld.so, so that's all the probe sees, and it picks guest_base 0. Then >> ld.so maps the real program at its fixed address, right on top of qemu. >> >> An easy way to hit it is ldd on FreeBSD. In an aarch64 jail with the >> stock perl package: >> >> # ldd /usr/local/bin/perl >> /usr/local/bin/perl: >> ld-elf.so.1: /usr/local/bin/perl: mmap of entire address space failed: Cannot allocate memory > > Mm, right. > >> FreeBSD can't link static PIE, so qemu-*-static is always linked at >> 0x200000. > > That's unfortunate. We could explicitly link somewhere high in the address space, perhaps. E.g. > > -Wl,--image-base,0x80000000000 > ? Rick, will you check and maybe come up with another patch? Helge
© 2016 - 2026 Red Hat, Inc.