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;

Reply via email to