diff mbox series

linux-user: mremap fails with EFAULT if address range overlaps with stack guard

Message ID 20200305210534.28392-1-tobias.koch@nonterra.com
State New
Headers show
Series linux-user: mremap fails with EFAULT if address range overlaps with stack guard | expand

Commit Message

Tobias Koch March 5, 2020, 9:05 p.m. UTC
If the address range starting at old_address overlaps with the stack guard it
is invalid and mremap must fail with EFAULT. The musl c library relies on this
behavior to detect the stack size, which it does by doing consecutive mremaps
until it hits the stack guard. Without this patch, software (such as the Ruby
interpreter) that calls pthread_getattr_np under musl will crash on 32 bit
targets emulated on a 64 bit host.

Signed-off-by: Tobias Koch <tobias.koch@nonterra.com>
---
 linux-user/mmap.c | 26 ++++++++++++++++++++++++++
 1 file changed, 26 insertions(+)

Comments

Laurent Vivier May 7, 2020, 2:35 p.m. UTC | #1
Le 05/03/2020 à 22:05, Tobias Koch a écrit :
> If the address range starting at old_address overlaps with the stack guard it
> is invalid and mremap must fail with EFAULT. The musl c library relies on this
> behavior to detect the stack size, which it does by doing consecutive mremaps
> until it hits the stack guard. Without this patch, software (such as the Ruby
> interpreter) that calls pthread_getattr_np under musl will crash on 32 bit
> targets emulated on a 64 bit host.

Could you share some pointers to the code that is doing this?

We have already this kind of code in linux-user/elfload.c,
setup_arg_pages(): could you check why it doesn't work?

Thanks,
Laurent
Tobias Koch June 15, 2020, 7:17 a.m. UTC | #2
Hi Laurent,

the code in musl libc probing the stack is in

    https://git.musl-libc.org/cgit/musl/plain/src/thread/pthread_getattr_np.c

The setup in elfload.c does work, but only when reserved_va is not set. In that case, any stack guard violation is
handled by the host kernel and thus results in the expected EFAULT.

However, in case of e.g. a 32bit target being emulated on a 64bit host, reserved_va is set and the current code in
mmap.c will only produce a more generic ENOMEM, deviating from the kernel's behavior.


On 5/7/20 5:35 PM, Laurent Vivier wrote:
> Le 05/03/2020 à 22:05, Tobias Koch a écrit :
>> If the address range starting at old_address overlaps with the stack guard it
>> is invalid and mremap must fail with EFAULT. The musl c library relies on this
>> behavior to detect the stack size, which it does by doing consecutive mremaps
>> until it hits the stack guard. Without this patch, software (such as the Ruby
>> interpreter) that calls pthread_getattr_np under musl will crash on 32 bit
>> targets emulated on a 64 bit host.
> Could you share some pointers to the code that is doing this?
>
> We have already this kind of code in linux-user/elfload.c,
> setup_arg_pages(): could you check why it doesn't work?
>
> Thanks,
> Laurent
Tobias Koch June 15, 2020, 8:28 p.m. UTC | #3
Hm, I see I need to have another look at this :)

On 6/15/20 10:17 AM, Tobias Koch wrote:
> Hi Laurent,
>
> the code in musl libc probing the stack is in
>
>     https://git.musl-libc.org/cgit/musl/plain/src/thread/pthread_getattr_np.c
>
> The setup in elfload.c does work, but only when reserved_va is not set. In that case, any stack guard violation is
> handled by the host kernel and thus results in the expected EFAULT.
>
> However, in case of e.g. a 32bit target being emulated on a 64bit host, reserved_va is set and the current code in
> mmap.c will only produce a more generic ENOMEM, deviating from the kernel's behavior.
>
>
> On 5/7/20 5:35 PM, Laurent Vivier wrote:
>> Le 05/03/2020 à 22:05, Tobias Koch a écrit :
>>> If the address range starting at old_address overlaps with the stack guard it
>>> is invalid and mremap must fail with EFAULT. The musl c library relies on this
>>> behavior to detect the stack size, which it does by doing consecutive mremaps
>>> until it hits the stack guard. Without this patch, software (such as the Ruby
>>> interpreter) that calls pthread_getattr_np under musl will crash on 32 bit
>>> targets emulated on a 64 bit host.
>> Could you share some pointers to the code that is doing this?
>>
>> We have already this kind of code in linux-user/elfload.c,
>> setup_arg_pages(): could you check why it doesn't work?
>>
>> Thanks,
>> Laurent
Tobias Koch June 15, 2020, 9:10 p.m. UTC | #4
Ok, so according to the manpage, mremap generates EFAULT when "the range old_address to old_address+old_size is an
invalid virtual memory address for this process". This is what the kernel does for the stack guard. However, the
mappings in setup_arg_pages() will only ever provoke an ENOMEM, because there is no artifical way to turn a page into an
invalid address. So as long as target bits >= host bits, this works as expected and EFAULT is generated, because then
mremap is basically passed through and the kernel responds directly. But when reserved_va is set, this needs to be
special-cased to fake kernel behavior.

I'm open to other suggestions. I also understand that the code duplication in elfload.c and mmap.c to handle this is
undesirable, but the most viable alternative seems to be introducing more globals.

On 6/15/20 11:28 PM, Tobias Koch wrote:
> Hm, I see I need to have another look at this :)
>
> On 6/15/20 10:17 AM, Tobias Koch wrote:
>> Hi Laurent,
>>
>> the code in musl libc probing the stack is in
>>
>>     https://git.musl-libc.org/cgit/musl/plain/src/thread/pthread_getattr_np.c
>>
>> The setup in elfload.c does work, but only when reserved_va is not set. In that case, any stack guard violation is
>> handled by the host kernel and thus results in the expected EFAULT.
>>
>> However, in case of e.g. a 32bit target being emulated on a 64bit host, reserved_va is set and the current code in
>> mmap.c will only produce a more generic ENOMEM, deviating from the kernel's behavior.
>>
>>
>> On 5/7/20 5:35 PM, Laurent Vivier wrote:
>>> Le 05/03/2020 à 22:05, Tobias Koch a écrit :
>>>> If the address range starting at old_address overlaps with the stack guard it
>>>> is invalid and mremap must fail with EFAULT. The musl c library relies on this
>>>> behavior to detect the stack size, which it does by doing consecutive mremaps
>>>> until it hits the stack guard. Without this patch, software (such as the Ruby
>>>> interpreter) that calls pthread_getattr_np under musl will crash on 32 bit
>>>> targets emulated on a 64 bit host.
>>> Could you share some pointers to the code that is doing this?
>>>
>>> We have already this kind of code in linux-user/elfload.c,
>>> setup_arg_pages(): could you check why it doesn't work?
Tobias Koch July 4, 2020, 2:44 p.m. UTC | #5
Ping

On 6/16/20 12:10 AM, Tobias Koch wrote:
> Ok, so according to the manpage, mremap generates EFAULT when "the range old_address to old_address+old_size is an
> invalid virtual memory address for this process". This is what the kernel does for the stack guard. However, the
> mappings in setup_arg_pages() will only ever provoke an ENOMEM, because there is no artifical way to turn a page into an
> invalid address. So as long as target bits >= host bits, this works as expected and EFAULT is generated, because then
> mremap is basically passed through and the kernel responds directly. But when reserved_va is set, this needs to be
> special-cased to fake kernel behavior.
> 
> I'm open to other suggestions. I also understand that the code duplication in elfload.c and mmap.c to handle this is
> undesirable, but the most viable alternative seems to be introducing more globals.
> 
> On 6/15/20 11:28 PM, Tobias Koch wrote:
>> Hm, I see I need to have another look at this :)
>>
>> On 6/15/20 10:17 AM, Tobias Koch wrote:
>>> Hi Laurent,
>>>
>>> the code in musl libc probing the stack is in
>>>
>>>     https://git.musl-libc.org/cgit/musl/plain/src/thread/pthread_getattr_np.c
>>>
>>> The setup in elfload.c does work, but only when reserved_va is not set. In that case, any stack guard violation is
>>> handled by the host kernel and thus results in the expected EFAULT.
>>>
>>> However, in case of e.g. a 32bit target being emulated on a 64bit host, reserved_va is set and the current code in
>>> mmap.c will only produce a more generic ENOMEM, deviating from the kernel's behavior.
>>>
>>>
>>> On 5/7/20 5:35 PM, Laurent Vivier wrote:
>>>> Le 05/03/2020 à 22:05, Tobias Koch a écrit :
>>>>> If the address range starting at old_address overlaps with the stack guard it
>>>>> is invalid and mremap must fail with EFAULT. The musl c library relies on this
>>>>> behavior to detect the stack size, which it does by doing consecutive mremaps
>>>>> until it hits the stack guard. Without this patch, software (such as the Ruby
>>>>> interpreter) that calls pthread_getattr_np under musl will crash on 32 bit
>>>>> targets emulated on a 64 bit host.
>>>> Could you share some pointers to the code that is doing this?
>>>>
>>>> We have already this kind of code in linux-user/elfload.c,
>>>> setup_arg_pages(): could you check why it doesn't work?
>
diff mbox series

Patch

diff --git a/linux-user/mmap.c b/linux-user/mmap.c
index 8685f02e7e..62cddbd072 100644
--- a/linux-user/mmap.c
+++ b/linux-user/mmap.c
@@ -651,6 +651,12 @@  int target_munmap(abi_ulong start, abi_ulong len)
     return ret;
 }
 
+#ifdef TARGET_HPPA
+#define STACK_GROWS_DOWN 0
+#else
+#define STACK_GROWS_DOWN 1
+#endif
+
 abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
                        abi_ulong new_size, unsigned long flags,
                        abi_ulong new_addr)
@@ -665,6 +671,26 @@  abi_long target_mremap(abi_ulong old_addr, abi_ulong old_size,
         return -1;
     }
 
+    /* Check that we're not overlapping with the stack guard. */
+    if (reserved_va) {
+        abi_ulong guard_size, guard_start;
+
+        guard_size = TARGET_PAGE_SIZE < qemu_real_host_page_size ?
+            qemu_real_host_page_size : TARGET_PAGE_SIZE;
+
+        if (STACK_GROWS_DOWN) {
+            guard_start = reserved_va - guest_stack_size - guard_size;
+        } else {
+            guard_start = reserved_va - guard_size;
+        }
+
+        if (guard_start < old_addr + old_size &&
+             old_addr < guard_start + guard_size) {
+            errno = EFAULT;
+            return -1;
+        }
+    }
+
     mmap_lock();
 
     if (flags & MREMAP_FIXED) {