[PATCH v2] linux-user: Fix mremap() with old_size == 0

Razvan Ghiorghe posted 1 patch 6 days, 2 hours ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260201010436.5735-1-razvanghiorghe16@gmail.com
Maintainers: Laurent Vivier <laurent@vivier.eu>, Pierrick Bouvier <pierrick.bouvier@linaro.org>
linux-user/mmap.c | 32 ++++++++++++++++++++++++++++++++
1 file changed, 32 insertions(+)
[PATCH v2] linux-user: Fix mremap() with old_size == 0
Posted by Razvan Ghiorghe 6 days, 2 hours ago
When old_size is zero and old_address refers to a shareable mapping,
mremap() should create a new mapping of the same pages according to the
mremap(2) man page. The MREMAP_MAYMOVE flag must be specified in this case.
Previously, QEMU's target_mremap() rejected this valid case with EFAULT during
the initial validation, before checking for the special old_size == 0 behaviour.

This patch adds proper handling for old_size == 0:
- Validates that MREMAP_MAYMOVE flag is set (required by man spec)
- Passes the call through to the host mremap()
- Creates a new mapping without invalidating the original, with both
beeing valid and sharing the same physical memory frames.

Tested with the reproducer from the issue on qemu-riscv64.
Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3105
Signed-off-by: Razvan Ghiorghe <razvanghiorghe16@gmail.com>
---
 linux-user/mmap.c | 32 ++++++++++++++++++++++++++++++++
 1 file changed, 32 insertions(+)

diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index 4bcfaf7894..b1a84eb60a 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -1117,6 +1117,38 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
         errno = EINVAL;
         return -1;
     }
+
+    if (!old_size) {
+        if (!(flags & MREMAP_MAYMOVE)) {
+            errno = EINVAL;
+            return -1;
+        }
+        mmap_lock();
+        if (flags & MREMAP_FIXED) {
+            host_addr = mremap(g2h_untagged(old_addr), old_size, new_size,
+                             flags, g2h_untagged(new_addr));
+        } else {
+            host_addr = mremap(g2h_untagged(old_addr), old_size, new_size,
+                             flags);
+        }
+
+        if (host_addr == MAP_FAILED) {
+            mmap_unlock();
+            return -1;
+        }
+        new_addr = h2g(host_addr);
+        prot = page_get_flags(old_addr);
+        /*
+         * For old_size zero, there is nothing to clear at old_addr.
+         * Only set the flags for the new mapping. They both are valid.
+         */
+        page_set_flags(new_addr, new_addr + new_size - 1,
+                       prot | PAGE_VALID, PAGE_VALID);
+        shm_region_rm_complete(new_addr, new_addr + new_size - 1);
+        mmap_unlock();
+        return new_addr;
+    }
+
     if (!guest_range_valid_untagged(old_addr, old_size)) {
         errno = EFAULT;
         return -1;
-- 
2.43.0
Re: [PATCH v2] linux-user: Fix mremap() with old_size == 0
Posted by Richard Henderson 3 days ago
On 2/1/26 11:03, Razvan Ghiorghe wrote:
> When old_size is zero and old_address refers to a shareable mapping,
> mremap() should create a new mapping of the same pages according to the
> mremap(2) man page. The MREMAP_MAYMOVE flag must be specified in this case.
> Previously, QEMU's target_mremap() rejected this valid case with EFAULT during
> the initial validation, before checking for the special old_size == 0 behaviour.
> 
> This patch adds proper handling for old_size == 0:
> - Validates that MREMAP_MAYMOVE flag is set (required by man spec)
> - Passes the call through to the host mremap()
> - Creates a new mapping without invalidating the original, with both
> beeing valid and sharing the same physical memory frames.
> 
> Tested with the reproducer from the issue on qemu-riscv64.
> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3105
> Signed-off-by: Razvan Ghiorghe <razvanghiorghe16@gmail.com>
> ---
>   linux-user/mmap.c | 32 ++++++++++++++++++++++++++++++++
>   1 file changed, 32 insertions(+)
> 
> diff --git a/linux-user/mmap.c b/linux-user/mmap.c
> index 4bcfaf7894..b1a84eb60a 100644
> --- a/linux-user/mmap.c
> +++ b/linux-user/mmap.c
> @@ -1117,6 +1117,38 @@ abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
>           errno = EINVAL;
>           return -1;
>       }
> +
> +    if (!old_size) {
> +        if (!(flags & MREMAP_MAYMOVE)) {
> +            errno = EINVAL;
> +            return -1;
> +        }
> +        mmap_lock();
> +        if (flags & MREMAP_FIXED) {
> +            host_addr = mremap(g2h_untagged(old_addr), old_size, new_size,
> +                             flags, g2h_untagged(new_addr));
> +        } else {
> +            host_addr = mremap(g2h_untagged(old_addr), old_size, new_size,
> +                             flags);
> +        }

This probably doesn't work with host page size != target page size.

The non-FIXED path doesn't work with reserved_va, as qemu needs to handle the address 
space itself for that case.  See the top of target_mmap__locked.

> +
> +        if (host_addr == MAP_FAILED) {
> +            mmap_unlock();
> +            return -1;
> +        }
> +        new_addr = h2g(host_addr);
> +        prot = page_get_flags(old_addr);
> +        /*
> +         * For old_size zero, there is nothing to clear at old_addr.
> +         * Only set the flags for the new mapping. They both are valid.
> +         */
> +        page_set_flags(new_addr, new_addr + new_size - 1,
> +                       prot | PAGE_VALID, PAGE_VALID);
> +        shm_region_rm_complete(new_addr, new_addr + new_size - 1);

Don't you need to copy the shm data?
Tracing all this through linux/mm/mremap.c is non-trivial...


r~