diff mbox

[v5,05/11] linux-user: fix mmap/munmap/mprotect/mremap/shmat

Message ID 20180307063651.4421-1-jcmvbkbc@gmail.com (mailing list archive)
State New, archived
Headers show

Commit Message

Max Filippov March 7, 2018, 6:36 a.m. UTC
In linux-user QEMU that runs for a target with TARGET_ABI_BITS bigger
than L1_MAP_ADDR_SPACE_BITS an assertion in page_set_flags fires when
mmap, munmap, mprotect, mremap or shmat is called for an address outside
the guest address space. mmap and mprotect should return ENOMEM in such
case.

Change definition of GUEST_ADDR_MAX to always be the last valid guest
address. Account for this change in open_self_maps.
Add macro guest_addr_valid that verifies if the guest address is valid.
Add function guest_range_valid that verifies if address range is within
guest address space and does not wrap around. Use that macro in
mmap/munmap/mprotect/mremap/shmat for error checking.

Cc: qemu-stable@nongnu.org
Cc: Riku Voipio <riku.voipio@iki.fi>
Cc: Laurent Vivier <laurent@vivier.eu>
Signed-off-by: Max Filippov <jcmvbkbc@gmail.com>
---
Changes v4->v5:
- change definition of GUEST_ADDR_MAX to always be the last valid guest
  address. Account for this change in guest_addr_valid and open_self_maps.
- turn guest_range_valid into a function.

Changes v3->v4:
- change GUEST_ADDR_MAX and h2g_valid definitions as suggested by Laurent
  Vivier.

Changes v2->v3:
- fix comparison in guest_valid: it must be 'less' to preserve the existing
  functionality, not 'less or equal'.
- fix guest_range_valid: it may not use guest_valid, because single range
  that occupies all of the guest address space is valid.

 include/exec/cpu-all.h  |  6 +++++-
 include/exec/cpu_ldst.h | 19 ++++++++++---------
 linux-user/mmap.c       | 20 +++++++++++++++-----
 linux-user/syscall.c    |  5 ++++-
 4 files changed, 34 insertions(+), 16 deletions(-)

Comments

Laurent Vivier March 7, 2018, 10:08 a.m. UTC | #1
Le 07/03/2018 à 07:36, Max Filippov a écrit :
> In linux-user QEMU that runs for a target with TARGET_ABI_BITS bigger
> than L1_MAP_ADDR_SPACE_BITS an assertion in page_set_flags fires when
> mmap, munmap, mprotect, mremap or shmat is called for an address outside
> the guest address space. mmap and mprotect should return ENOMEM in such
> case.
> 
> Change definition of GUEST_ADDR_MAX to always be the last valid guest
> address. Account for this change in open_self_maps.
> Add macro guest_addr_valid that verifies if the guest address is valid.
> Add function guest_range_valid that verifies if address range is within
> guest address space and does not wrap around. Use that macro in
> mmap/munmap/mprotect/mremap/shmat for error checking.
> 
> Cc: qemu-stable@nongnu.org
> Cc: Riku Voipio <riku.voipio@iki.fi>
> Cc: Laurent Vivier <laurent@vivier.eu>
> Signed-off-by: Max Filippov <jcmvbkbc@gmail.com>
> ---
> Changes v4->v5:
> - change definition of GUEST_ADDR_MAX to always be the last valid guest
>   address. Account for this change in guest_addr_valid and open_self_maps.
> - turn guest_range_valid into a function.
> 
> Changes v3->v4:
> - change GUEST_ADDR_MAX and h2g_valid definitions as suggested by Laurent
>   Vivier.
> 
> Changes v2->v3:
> - fix comparison in guest_valid: it must be 'less' to preserve the existing
>   functionality, not 'less or equal'.
> - fix guest_range_valid: it may not use guest_valid, because single range
>   that occupies all of the guest address space is valid.
> 
>  include/exec/cpu-all.h  |  6 +++++-
>  include/exec/cpu_ldst.h | 19 ++++++++++---------
>  linux-user/mmap.c       | 20 +++++++++++++++-----
>  linux-user/syscall.c    |  5 ++++-
>  4 files changed, 34 insertions(+), 16 deletions(-)
> 
> diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h
> index 0b141683f095..f4fa94e9669d 100644
> --- a/include/exec/cpu-all.h
> +++ b/include/exec/cpu-all.h
> @@ -159,8 +159,12 @@ extern unsigned long guest_base;
>  extern int have_guest_base;
>  extern unsigned long reserved_va;
>  
> -#define GUEST_ADDR_MAX (reserved_va ? reserved_va : \
> +#if HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS
> +#define GUEST_ADDR_MAX (~0ul)
> +#else
> +#define GUEST_ADDR_MAX (reserved_va ? reserved_va - 1 : \
>                                      (1ul << TARGET_VIRT_ADDR_SPACE_BITS) - 1)
> +#endif
>  #else
>  
>  #include "exec/hwaddr.h"
> diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h
> index 191f2e962a3c..313664e9dae8 100644
> --- a/include/exec/cpu_ldst.h
> +++ b/include/exec/cpu_ldst.h
> @@ -51,15 +51,16 @@
>  /* All direct uses of g2h and h2g need to go away for usermode softmmu.  */
>  #define g2h(x) ((void *)((unsigned long)(target_ulong)(x) + guest_base))
>  
> -#if HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS
> -#define h2g_valid(x) 1
> -#else
> -#define h2g_valid(x) ({ \
> -    unsigned long __guest = (unsigned long)(x) - guest_base; \
> -    (__guest < (1ul << TARGET_VIRT_ADDR_SPACE_BITS)) && \
> -    (!reserved_va || (__guest < reserved_va)); \
> -})
> -#endif
> +#define guest_addr_valid(x) ((x) <= GUEST_ADDR_MAX)
> +#define h2g_valid(x) guest_addr_valid((unsigned long)(x) - guest_base)
> +
> +static inline int guest_range_valid(unsigned long start, unsigned long len)
> +{
> +    if (len)
> +        return guest_addr_valid(len - 1) && start <= GUEST_ADDR_MAX - len + 1;
> +    else
> +        return guest_addr_valid(start);
> +}

I think we can consider len == 0 is invalid and use only:

  return start + (len - 1) <= GUEST_ADDR_MAX;

Thanks,
Laurent
Max Filippov March 7, 2018, 5:45 p.m. UTC | #2
On Wed, Mar 7, 2018 at 2:08 AM, Laurent Vivier <laurent@vivier.eu> wrote:
>> +static inline int guest_range_valid(unsigned long start, unsigned long len)
>> +{
>> +    if (len)
>> +        return guest_addr_valid(len - 1) && start <= GUEST_ADDR_MAX - len + 1;
>> +    else
>> +        return guest_addr_valid(start);
>> +}
>
> I think we can consider len == 0 is invalid and use only:
>
>   return start + (len - 1) <= GUEST_ADDR_MAX;

start + len - 1 may wrap around, that's why I first validate len and then have
len at the right side of the comparison. I.e. if we drop check for len == 0 I'd
still write it as

  guest_addr_valid(len - 1) && start <= GUEST_ADDR_MAX - len + 1;
Laurent Vivier March 7, 2018, 8:13 p.m. UTC | #3
Le 07/03/2018 à 18:45, Max Filippov a écrit :
> On Wed, Mar 7, 2018 at 2:08 AM, Laurent Vivier <laurent@vivier.eu> wrote:
>>> +static inline int guest_range_valid(unsigned long start, unsigned long len)
>>> +{
>>> +    if (len)
>>> +        return guest_addr_valid(len - 1) && start <= GUEST_ADDR_MAX - len + 1;
>>> +    else
>>> +        return guest_addr_valid(start);
>>> +}
>>
>> I think we can consider len == 0 is invalid and use only:
>>
>>   return start + (len - 1) <= GUEST_ADDR_MAX;
> 
> start + len - 1 may wrap around, that's why I first validate len and then have
> len at the right side of the comparison. I.e. if we drop check for len == 0 I'd
> still write it as
> 
>   guest_addr_valid(len - 1) && start <= GUEST_ADDR_MAX - len + 1;
> 

Yes, you're right.

it would be clearer to write:

    len - 1 <= GUEST_ADDR_MAX && start <= GUEST_ADDR_MAX - len + 1;

but it's only cosmetic.

Thanks,
Laurent
diff mbox

Patch

diff --git a/include/exec/cpu-all.h b/include/exec/cpu-all.h
index 0b141683f095..f4fa94e9669d 100644
--- a/include/exec/cpu-all.h
+++ b/include/exec/cpu-all.h
@@ -159,8 +159,12 @@  extern unsigned long guest_base;
 extern int have_guest_base;
 extern unsigned long reserved_va;
 
-#define GUEST_ADDR_MAX (reserved_va ? reserved_va : \
+#if HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS
+#define GUEST_ADDR_MAX (~0ul)
+#else
+#define GUEST_ADDR_MAX (reserved_va ? reserved_va - 1 : \
                                     (1ul << TARGET_VIRT_ADDR_SPACE_BITS) - 1)
+#endif
 #else
 
 #include "exec/hwaddr.h"
diff --git a/include/exec/cpu_ldst.h b/include/exec/cpu_ldst.h
index 191f2e962a3c..313664e9dae8 100644
--- a/include/exec/cpu_ldst.h
+++ b/include/exec/cpu_ldst.h
@@ -51,15 +51,16 @@ 
 /* All direct uses of g2h and h2g need to go away for usermode softmmu.  */
 #define g2h(x) ((void *)((unsigned long)(target_ulong)(x) + guest_base))
 
-#if HOST_LONG_BITS <= TARGET_VIRT_ADDR_SPACE_BITS
-#define h2g_valid(x) 1
-#else
-#define h2g_valid(x) ({ \
-    unsigned long __guest = (unsigned long)(x) - guest_base; \
-    (__guest < (1ul << TARGET_VIRT_ADDR_SPACE_BITS)) && \
-    (!reserved_va || (__guest < reserved_va)); \
-})
-#endif
+#define guest_addr_valid(x) ((x) <= GUEST_ADDR_MAX)
+#define h2g_valid(x) guest_addr_valid((unsigned long)(x) - guest_base)
+
+static inline int guest_range_valid(unsigned long start, unsigned long len)
+{
+    if (len)
+        return guest_addr_valid(len - 1) && start <= GUEST_ADDR_MAX - len + 1;
+    else
+        return guest_addr_valid(start);
+}
 
 #define h2g_nocheck(x) ({ \
     unsigned long __ret = (unsigned long)(x) - guest_base; \
diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index 0fbfd6dff20d..df81f9b803b6 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -80,8 +80,9 @@  int target_mprotect(abi_ulong start, abi_ulong len, int prot)
         return -EINVAL;
     len = TARGET_PAGE_ALIGN(len);
     end = start + len;
-    if (end < start)
-        return -EINVAL;
+    if (!guest_range_valid(start, len)) {
+        return -ENOMEM;
+    }
     prot &= PROT_READ | PROT_WRITE | PROT_EXEC;
     if (len == 0)
         return 0;
@@ -481,8 +482,8 @@  abi_long target_mmap(abi_ulong start, abi_ulong len, int prot,
 	 * It can fail only on 64-bit host with 32-bit target.
 	 * On any other target/host host mmap() handles this error correctly.
 	 */
-        if ((unsigned long)start + len - 1 > (abi_ulong) -1) {
-            errno = EINVAL;
+        if (!guest_range_valid(start, len)) {
+            errno = ENOMEM;
             goto fail;
         }
 
@@ -622,8 +623,10 @@  int target_munmap(abi_ulong start, abi_ulong len)
     if (start & ~TARGET_PAGE_MASK)
         return -EINVAL;
     len = TARGET_PAGE_ALIGN(len);
-    if (len == 0)
+    if (len == 0 || !guest_range_valid(start, len)) {
         return -EINVAL;
+    }
+
     mmap_lock();
     end = start + len;
     real_start = start & qemu_host_page_mask;
@@ -678,6 +681,13 @@  abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
     int prot;
     void *host_addr;
 
+    if (!guest_range_valid(old_addr, old_size) ||
+        ((flags & MREMAP_FIXED) &&
+         !guest_range_valid(new_addr, new_size))) {
+        errno = ENOMEM;
+        return -1;
+    }
+
     mmap_lock();
 
     if (flags & MREMAP_FIXED) {
diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index e24f43c4a259..809644742aff 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -4900,6 +4900,9 @@  static inline abi_ulong do_shmat(CPUArchState *cpu_env,
             return -TARGET_EINVAL;
         }
     }
+    if (!guest_range_valid(shmaddr, shm_info.shm_segsz)) {
+        return -TARGET_EINVAL;
+    }
 
     mmap_lock();
 
@@ -7468,7 +7471,7 @@  static int open_self_maps(void *cpu_env, int fd)
         }
         if (h2g_valid(min)) {
             int flags = page_get_flags(h2g(min));
-            max = h2g_valid(max - 1) ? max : (uintptr_t)g2h(GUEST_ADDR_MAX);
+            max = h2g_valid(max - 1) ? max : (uintptr_t)g2h(GUEST_ADDR_MAX) + 1;
             if (page_check_range(h2g(min), max - min, flags) == -1) {
                 continue;
             }