From: Razvan Ghiorghe <[email protected]> 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 being valid and sharing the same physical memory frames. - Ensures the new mapping address falls within the valid guest address region before returning it to the guest. Tested with the reproducer from the issue on qemu-riscv64, qemu-hppa, and qemu-aarch64. Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3105 Signed-off-by: Razvan Ghiorghe <[email protected]> Tested-by: Helge Deller <[email protected]> Reviewed-by: Helge Deller <[email protected]> Signed-off-by: Helge Deller <[email protected]> (cherry picked from commit 5e5b278d2b1b81fc2b5ca09dba4848f81cd3a718) (mjt, rth: backport to pre-f55fc1c092 "accel/tcg: Add clear_flags argument to page_set_flags") Signed-off-by: Michael Tokarev <[email protected]> diff --git a/linux-user/mmap.c b/linux-user/mmap.c index 5622a01123..230bc2c094 100644 --- a/linux-user/mmap.c +++ b/linux-user/mmap.c @@ -1118,6 +1118,58 @@ 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 { + /* + * We ensure that the new mapping stands in the + * region of guest mappable addresses. + */ + abi_ulong mmap_start; + + mmap_start = mmap_find_vma(0, new_size, TARGET_PAGE_SIZE); + + if (mmap_start == -1) { + errno = ENOMEM; + mmap_unlock(); + return -1; + } + + host_addr = mremap(g2h_untagged(old_addr), old_size, new_size, + flags | MREMAP_FIXED, g2h_untagged(mmap_start)); + + new_addr = mmap_start; + } + + if (host_addr == MAP_FAILED) { + mmap_unlock(); + return -1; + } + + if (flags & MREMAP_FIXED) { + 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_RESET); + 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.47.3
