* [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