[PATCH] common-user: Skip the identity map when qemu is loaded low

Rick Richard posted 1 patch 3 weeks ago
common-user/probe-guest-base.c | 9 +++++++--
include/user/probe-guest-base.h | 3 +++
2 files changed, 10 insertions(+), 2 deletions(-)
[PATCH] common-user: Skip the identity map when qemu is loaded low
Posted by Rick Richard 3 weeks ago
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;
Re: [PATCH] common-user: Skip the identity map when qemu is loaded low
Posted by Richard Henderson 1 week, 6 days ago
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~
Re: [PATCH] common-user: Skip the identity map when qemu is loaded low
Posted by Rick Richard 1 week, 5 days ago
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.


Re: [PATCH] common-user: Skip the identity map when qemu is loaded low
Posted by Richard Henderson 1 week, 3 days ago
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~

Re: [PATCH] common-user: Skip the identity map when qemu is loaded low
Posted by Rick Richard 1 week, 1 day ago
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.

Re: [PATCH] common-user: Skip the identity map when qemu is loaded low
Posted by Richard Henderson 1 week, 1 day ago
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~
Re: [PATCH] common-user: Skip the identity map when qemu is loaded low
Posted by Helge Deller 1 week, 2 days ago
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