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 <[email protected]> --- 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;
