* [PATCH v2 0/4] linux-user: fix several mremap bugs
@ 2025-11-17 17:09 Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 1/4] linux-user: fix mremap unmapping adjacent region Matthew Lugg
` (3 more replies)
0 siblings, 4 replies; 7+ messages in thread
From: Matthew Lugg @ 2025-11-17 17:09 UTC (permalink / raw)
To: qemu-devel; +Cc: laurent, peter.maydell, Matthew Lugg
This version of the series should address all feedback I received. The original
cover letter is replicated below.
I was recently debugging a strange crash in a downstream project which turned
out to be a QEMU bug related to the `mremap` implementation in linux-user. In
practice, this bug essentially led to arbitrary memory regions being unmapped
when a 32-bit guest, running on a 64-bit host, uses `mremap` to shrink a memory
mapping.
The first patch in this set resolves that bug. Since the patch is very simple,
and the bug is quite likely to be hit, I suspect that that commit is a good
candidate for qemu-stable.
The following two patches just resolve two more bugs I became aware of whilst
working on this code. I believe the messages in those patches contain all
necessary context. They are less critical and the fixes more complex, so are
likely not suitable for backporting into qemu-stable.
The final commits adds tcg tests for the fixed `mremap` behavior. The third fix
is unfortunately difficult to test programmatically, but I have confirmed that
it behaves as expected by observing the output of `strace qemu-i386 repro`,
where `repro` is the following C program:
#define _GNU_SOURCE
#include <stddef.h>
#include <sys/mman.h>
int main(void) {
char *a = mmap(NULL, 4097, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
char *b = mmap(NULL, 4097, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
mremap(b, 4097, 4097, MREMAP_FIXED | MREMAP_MAYMOVE, a);
// QEMU has now leaked a page of its memory reservation!
return 0;
}
Prior to the patch, as the comment says, QEMU leaks a page of its address space
reservation (i.e. the page becomes unmapped). After the patch, QEMU correctly
reclaims that page with `mmap`.
Matthew Lugg (4):
linux-user: fix mremap unmapping adjacent region
linux-user: fix mremap errors for invalid ranges
linux-user: fix reserved_va page leak in do_munmap
tests: add tcg coverage for fixed mremap bugs
linux-user/mmap.c | 16 ++++++++-----
tests/tcg/multiarch/test-mmap.c | 42 +++++++++++++++++++++++++++++++--
2 files changed, 50 insertions(+), 8 deletions(-)
--
2.51.2
^ permalink raw reply [flat|nested] 7+ messages in thread
* [PATCH v2 1/4] linux-user: fix mremap unmapping adjacent region
2025-11-17 17:09 [PATCH v2 0/4] linux-user: fix several mremap bugs Matthew Lugg
@ 2025-11-17 17:09 ` Matthew Lugg
2025-11-19 11:20 ` Richard Henderson
2025-11-17 17:09 ` [PATCH v2 2/4] linux-user: fix mremap errors for invalid ranges Matthew Lugg
` (2 subsequent siblings)
3 siblings, 1 reply; 7+ messages in thread
From: Matthew Lugg @ 2025-11-17 17:09 UTC (permalink / raw)
To: qemu-devel; +Cc: laurent, peter.maydell, Matthew Lugg
This typo meant that calls to `mremap` which shrink a mapping by some N
bytes would, when the virtual address space was pre-reserved (e.g.
32-bit guest on 64-bit host), unmap the N bytes following the *original*
mapping.
Signed-off-by: Matthew Lugg <mlugg@mlugg.co.uk>
---
linux-user/mmap.c | 3 ++-
1 file changed, 2 insertions(+), 1 deletion(-)
No changes from last revision, just rebased.
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index 423c77856a..ef3833a2bb 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -1171,7 +1171,8 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
errno = ENOMEM;
host_addr = MAP_FAILED;
} else if (reserved_va && old_size > new_size) {
- mmap_reserve_or_unmap(old_addr + old_size,
+ /* Re-reserve pages we just shrunk out of the mapping */
+ mmap_reserve_or_unmap(old_addr + new_size,
old_size - new_size);
}
}
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 2/4] linux-user: fix mremap errors for invalid ranges
2025-11-17 17:09 [PATCH v2 0/4] linux-user: fix several mremap bugs Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 1/4] linux-user: fix mremap unmapping adjacent region Matthew Lugg
@ 2025-11-17 17:09 ` Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 3/4] linux-user: fix reserved_va page leak in do_munmap Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 4/4] tests: add tcg coverage for fixed mremap bugs Matthew Lugg
3 siblings, 0 replies; 7+ messages in thread
From: Matthew Lugg @ 2025-11-17 17:09 UTC (permalink / raw)
To: qemu-devel; +Cc: laurent, peter.maydell, Matthew Lugg
If an address range given to `mremap` is invalid (exceeds addressing
bounds on the guest), we were previously returning `ENOMEM`, which is
not correct. The manpage and the Linux kernel implementation both agree
that if `old_addr`/`old_size` refer to an invalid address, `EFAULT` is
returned, and if `new_addr`/`new_size` refer to an invalid address,
`EINVAL` is returned.
Signed-off-by: Matthew Lugg <mlugg@mlugg.co.uk>
---
linux-user/mmap.c | 9 ++++++---
1 file changed, 6 insertions(+), 3 deletions(-)
Changes from last revision: the EINVAL case has been moved before the EFAULT
case in accordance with Richard's feedback.
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index ef3833a2bb..6163f1a0d1 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -1110,12 +1110,15 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
int prot;
void *host_addr;
- if (!guest_range_valid_untagged(old_addr, old_size) ||
- ((flags & MREMAP_FIXED) &&
+ if (((flags & MREMAP_FIXED) &&
!guest_range_valid_untagged(new_addr, new_size)) ||
((flags & MREMAP_MAYMOVE) == 0 &&
!guest_range_valid_untagged(old_addr, new_size))) {
- errno = ENOMEM;
+ errno = EINVAL;
+ return -1;
+ }
+ if (!guest_range_valid_untagged(old_addr, old_size)) {
+ errno = EFAULT;
return -1;
}
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 3/4] linux-user: fix reserved_va page leak in do_munmap
2025-11-17 17:09 [PATCH v2 0/4] linux-user: fix several mremap bugs Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 1/4] linux-user: fix mremap unmapping adjacent region Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 2/4] linux-user: fix mremap errors for invalid ranges Matthew Lugg
@ 2025-11-17 17:09 ` Matthew Lugg
2025-11-19 11:21 ` Richard Henderson
2025-11-17 17:09 ` [PATCH v2 4/4] tests: add tcg coverage for fixed mremap bugs Matthew Lugg
3 siblings, 1 reply; 7+ messages in thread
From: Matthew Lugg @ 2025-11-17 17:09 UTC (permalink / raw)
To: qemu-devel; +Cc: laurent, peter.maydell, Matthew Lugg
The old logic had an off-by-one bug. For instance, assuming 4k pages on
host and guest, if 'len' is '4097' (indicating to unmap 2 pages), then
'last = start + 4096', so 'real_last = start + 4095', so ultimately
'real_len = 4096'. I do not believe this could cause any observable bugs
in guests, because `target_munmap` page-aligns the length it passes in.
However, calls to this function in `target_mremap` do not page-align the
length, so those calls could "drop" pages, leading to a part of the
reserved region becoming unmapped. At worst, a host allocation could get
mapped into that hole, then clobbered by a new guest mapping.
Signed-off-by: Matthew Lugg <mlugg@mlugg.co.uk>
---
linux-user/mmap.c | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
The refactor in the last revision has been dropped in favour of a simple
two-line patch. The added 'ROUND_UP' on the first line of the diff is necessary
to prevent the last 'for (prot = 0, ...' in the function from considering a page
which 'len' partially covers.
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index 6163f1a0d1..4bcfaf7894 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -1029,9 +1029,9 @@ static int mmap_reserve_or_unmap(abi_ulong start, abi_ulong len)
void *host_start;
int prot;
- last = start + len - 1;
+ last = ROUND_UP(start + len, TARGET_PAGE_SIZE) - 1;
real_start = start & -host_page_size;
- real_last = ROUND_UP(last, host_page_size) - 1;
+ real_last = ROUND_UP(last + 1, host_page_size) - 1;
/*
* If guest pages remain on the first or last host pages,
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* [PATCH v2 4/4] tests: add tcg coverage for fixed mremap bugs
2025-11-17 17:09 [PATCH v2 0/4] linux-user: fix several mremap bugs Matthew Lugg
` (2 preceding siblings ...)
2025-11-17 17:09 ` [PATCH v2 3/4] linux-user: fix reserved_va page leak in do_munmap Matthew Lugg
@ 2025-11-17 17:09 ` Matthew Lugg
3 siblings, 0 replies; 7+ messages in thread
From: Matthew Lugg @ 2025-11-17 17:09 UTC (permalink / raw)
To: qemu-devel; +Cc: laurent, peter.maydell, Matthew Lugg
These tests cover the first two fixes in this patch series. The final
patch is not covered because the bug it fixes is not easily observable
by the guest.
Signed-off-by: Matthew Lugg <mlugg@mlugg.co.uk>
---
tests/tcg/multiarch/test-mmap.c | 42 +++++++++++++++++++++++++++++++--
1 file changed, 40 insertions(+), 2 deletions(-)
Changes from last revision: reverted changes to stdout format to keep the patch
focused. Note that the last line of the diff appears wrongly idented, but that
is actually because this file has a lot of incorrect hard tab identation, while
the diff correctly uses spaces.
diff --git a/tests/tcg/multiarch/test-mmap.c b/tests/tcg/multiarch/test-mmap.c
index 96257f8ebe..e297f4b1e9 100644
--- a/tests/tcg/multiarch/test-mmap.c
+++ b/tests/tcg/multiarch/test-mmap.c
@@ -22,6 +22,7 @@
* along with this program; if not, see <http://www.gnu.org/licenses/>.
*/
+#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <stdint.h>
@@ -41,7 +42,7 @@ do \
} \
} while (0)
-unsigned char *dummybuf;
+unsigned char *dummybuf; /* length is 2*pagesize */
static unsigned int pagesize;
static unsigned int pagemask;
int test_fd;
@@ -451,9 +452,45 @@ void check_invalid_mmaps(void)
fail_unless(addr == MAP_FAILED);
fail_unless(errno == ENOMEM);
+ /* Attempt to remap a region which exceeds the bounds of memory. */
+ addr = mremap((void *)((uintptr_t)pagesize * 10), SIZE_MAX & ~(size_t)pagemask, pagesize, 0);
+ fprintf(stdout, "%s mremap addr=%p", __func__, (void *)addr);
+ fail_unless(addr == MAP_FAILED);
+ fail_unless(errno == EFAULT);
+
fprintf(stdout, " passed\n");
}
+void check_shrink_mmaps(void)
+{
+ unsigned char *a, *b, *c;
+ a = mmap(NULL, pagesize * 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ b = mmap(NULL, pagesize * 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+ c = mmap(NULL, pagesize * 2, PROT_READ, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0);
+
+ fail_unless(a != MAP_FAILED);
+ fail_unless(b != MAP_FAILED);
+ fail_unless(c != MAP_FAILED);
+
+ /* Ensure we can read the full mappings */
+ memcpy(dummybuf, a, 2 * pagesize);
+ memcpy(dummybuf, b, 2 * pagesize);
+ memcpy(dummybuf, c, 2 * pagesize);
+
+ /* Shrink the middle mapping in-place; the others should be unaffected */
+ b = mremap(b, pagesize * 2, pagesize, 0);
+ fail_unless(b != MAP_FAILED);
+
+ /* Ensure we can still access all valid mappings */
+ memcpy(dummybuf, a, 2 * pagesize);
+ memcpy(dummybuf, b, pagesize);
+ memcpy(dummybuf, c, 2 * pagesize);
+
+ munmap(a, 2 * pagesize);
+ munmap(b, pagesize);
+ munmap(c, 2 * pagesize);
+}
+
int main(int argc, char **argv)
{
char tempname[] = "/tmp/.cmmapXXXXXX";
@@ -468,7 +505,7 @@ int main(int argc, char **argv)
/* Assume pagesize is a power of two. */
pagemask = pagesize - 1;
- dummybuf = malloc (pagesize);
+ dummybuf = malloc (pagesize * 2);
printf ("pagesize=%u pagemask=%x\n", pagesize, pagemask);
test_fd = mkstemp(tempname);
@@ -496,6 +533,7 @@ int main(int argc, char **argv)
check_file_fixed_eof_mmaps();
check_file_unfixed_eof_mmaps();
check_invalid_mmaps();
+ check_shrink_mmaps();
/* Fails at the moment. */
/* check_aligned_anonymous_fixed_mmaps_collide_with_host(); */
--
2.51.2
^ permalink raw reply related [flat|nested] 7+ messages in thread
* Re: [PATCH v2 1/4] linux-user: fix mremap unmapping adjacent region
2025-11-17 17:09 ` [PATCH v2 1/4] linux-user: fix mremap unmapping adjacent region Matthew Lugg
@ 2025-11-19 11:20 ` Richard Henderson
0 siblings, 0 replies; 7+ messages in thread
From: Richard Henderson @ 2025-11-19 11:20 UTC (permalink / raw)
To: Matthew Lugg, qemu-devel; +Cc: laurent, peter.maydell
On 11/17/25 18:09, Matthew Lugg wrote:
> This typo meant that calls to `mremap` which shrink a mapping by some N
> bytes would, when the virtual address space was pre-reserved (e.g.
> 32-bit guest on 64-bit host), unmap the N bytes following the*original*
> mapping.
>
> Signed-off-by: Matthew Lugg<mlugg@mlugg.co.uk>
> ---
> linux-user/mmap.c | 3 ++-
> 1 file changed, 2 insertions(+), 1 deletion(-)
>
> No changes from last revision, just rebased.
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
r~
^ permalink raw reply [flat|nested] 7+ messages in thread
* Re: [PATCH v2 3/4] linux-user: fix reserved_va page leak in do_munmap
2025-11-17 17:09 ` [PATCH v2 3/4] linux-user: fix reserved_va page leak in do_munmap Matthew Lugg
@ 2025-11-19 11:21 ` Richard Henderson
0 siblings, 0 replies; 7+ messages in thread
From: Richard Henderson @ 2025-11-19 11:21 UTC (permalink / raw)
To: Matthew Lugg, qemu-devel; +Cc: laurent, peter.maydell
On 11/17/25 18:09, Matthew Lugg wrote:
> The old logic had an off-by-one bug. For instance, assuming 4k pages on
> host and guest, if 'len' is '4097' (indicating to unmap 2 pages), then
> 'last = start + 4096', so 'real_last = start + 4095', so ultimately
> 'real_len = 4096'. I do not believe this could cause any observable bugs
> in guests, because `target_munmap` page-aligns the length it passes in.
> However, calls to this function in `target_mremap` do not page-align the
> length, so those calls could "drop" pages, leading to a part of the
> reserved region becoming unmapped. At worst, a host allocation could get
> mapped into that hole, then clobbered by a new guest mapping.
>
> Signed-off-by: Matthew Lugg<mlugg@mlugg.co.uk>
> ---
> linux-user/mmap.c | 4 ++--
> 1 file changed, 2 insertions(+), 2 deletions(-)
Reviewed-by: Richard Henderson <richard.henderson@linaro.org>
r~
^ permalink raw reply [flat|nested] 7+ messages in thread
end of thread, other threads:[~2025-11-19 11:22 UTC | newest]
Thread overview: 7+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-17 17:09 [PATCH v2 0/4] linux-user: fix several mremap bugs Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 1/4] linux-user: fix mremap unmapping adjacent region Matthew Lugg
2025-11-19 11:20 ` Richard Henderson
2025-11-17 17:09 ` [PATCH v2 2/4] linux-user: fix mremap errors for invalid ranges Matthew Lugg
2025-11-17 17:09 ` [PATCH v2 3/4] linux-user: fix reserved_va page leak in do_munmap Matthew Lugg
2025-11-19 11:21 ` Richard Henderson
2025-11-17 17:09 ` [PATCH v2 4/4] tests: add tcg coverage for fixed mremap bugs Matthew Lugg
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).