diff mbox

[v2,2/2] linux-user: allocate heap memory for execve arguments

Message ID 20170306071721.26708-3-ppandit@redhat.com (mailing list archive)
State New, archived
Headers show

Commit Message

Prasad Pandit March 6, 2017, 7:17 a.m. UTC
From: Prasad J Pandit <pjp@fedoraproject.org>

Arguments passed to execve(2) call from user program could
be large, allocating stack memory for them via alloca(3) call
would lead to bad behaviour. Use 'g_malloc0' to allocate memory
for such arguments.

Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
---
 linux-user/syscall.c | 7 +++++--
 1 file changed, 5 insertions(+), 2 deletions(-)

Update per: replace alloca() with g_malloc0()
  -> https://lists.gnu.org/archive/html/qemu-devel/2017-03/msg00750.html

Comments

Eric Blake March 6, 2017, 3:53 p.m. UTC | #1
On 03/06/2017 01:17 AM, P J P wrote:
> From: Prasad J Pandit <pjp@fedoraproject.org>
> 
> Arguments passed to execve(2) call from user program could
> be large, allocating stack memory for them via alloca(3) call
> would lead to bad behaviour. Use 'g_malloc0' to allocate memory
> for such arguments.
> 
> Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
> ---
>  linux-user/syscall.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)

Is this patch alone (without 1/2) sufficient to solve the problem?  If
so, then drop 1/2.

> 
> Update per: replace alloca() with g_malloc0()
>   -> https://lists.gnu.org/archive/html/qemu-devel/2017-03/msg00750.html
> 
> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index 86a4a9c..404fb0b 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -7800,8 +7800,8 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,

What version of qemu are you patching?  Line 7800 of current master is
nowhere near 'case TARGET_NR_execve:' (line 7899)

>                  ret = -TARGET_E2BIG;
>                  break;
>              }

and current master has 'goto efault' rather than directly setting ret at
this point.

> -            argp = alloca((argc + 1) * sizeof(void *));
> -            envp = alloca((envc + 1) * sizeof(void *));
> +            argp = g_malloc0((argc + 1) * sizeof(void *));
> +            envp = g_malloc0((envc + 1) * sizeof(void *));

Subject to a potential multiplication overflow.  I'd prefer:

g_new0(void *, argc + 1);

as that is guaranteed to not overflow.

>  
>              for (gp = guest_argp, q = argp; gp;
>                    gp += sizeof(abi_ulong), q++) {
> @@ -7862,6 +7862,9 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
>                      break;
>                  unlock_user(*q, addr, 0);
>              }
> +
> +            g_free(argp);
> +            g_free(envp);
>          }
>          break;
>      case TARGET_NR_chdir:
>
Peter Maydell March 6, 2017, 3:57 p.m. UTC | #2
On 6 March 2017 at 07:17, P J P <ppandit@redhat.com> wrote:
> From: Prasad J Pandit <pjp@fedoraproject.org>
>
> Arguments passed to execve(2) call from user program could
> be large, allocating stack memory for them via alloca(3) call
> would lead to bad behaviour. Use 'g_malloc0' to allocate memory
> for such arguments.
>
> Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
> ---
>  linux-user/syscall.c | 7 +++++--
>  1 file changed, 5 insertions(+), 2 deletions(-)
>
> Update per: replace alloca() with g_malloc0()
>   -> https://lists.gnu.org/archive/html/qemu-devel/2017-03/msg00750.html
>
> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
> index 86a4a9c..404fb0b 100644
> --- a/linux-user/syscall.c
> +++ b/linux-user/syscall.c
> @@ -7800,8 +7800,8 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
>                  ret = -TARGET_E2BIG;
>                  break;
>              }
> -            argp = alloca((argc + 1) * sizeof(void *));
> -            envp = alloca((envc + 1) * sizeof(void *));
> +            argp = g_malloc0((argc + 1) * sizeof(void *));
> +            envp = g_malloc0((envc + 1) * sizeof(void *));
>
>              for (gp = guest_argp, q = argp; gp;
>                    gp += sizeof(abi_ulong), q++) {
> @@ -7862,6 +7862,9 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
>                      break;
>                  unlock_user(*q, addr, 0);
>              }
> +
> +            g_free(argp);
> +            g_free(envp);
>          }
>          break;
>      case TARGET_NR_chdir:

Better to use the _try_ glib allocation functions and fail
the syscall on allocation failure, maybe?

thanks
-- PMM
Eric Blake March 6, 2017, 4:08 p.m. UTC | #3
On 03/06/2017 09:53 AM, Eric Blake wrote:
> On 03/06/2017 01:17 AM, P J P wrote:
>> From: Prasad J Pandit <pjp@fedoraproject.org>
>>
>> Arguments passed to execve(2) call from user program could
>> be large, allocating stack memory for them via alloca(3) call
>> would lead to bad behaviour. Use 'g_malloc0' to allocate memory
>> for such arguments.
>>
>> Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
>> ---
>>  linux-user/syscall.c | 7 +++++--
>>  1 file changed, 5 insertions(+), 2 deletions(-)
> 
> Is this patch alone (without 1/2) sufficient to solve the problem?  If
> so, then drop 1/2.
> 
>>
>> Update per: replace alloca() with g_malloc0()
>>   -> https://lists.gnu.org/archive/html/qemu-devel/2017-03/msg00750.html
>>
>> diff --git a/linux-user/syscall.c b/linux-user/syscall.c
>> index 86a4a9c..404fb0b 100644
>> --- a/linux-user/syscall.c
>> +++ b/linux-user/syscall.c
>> @@ -7800,8 +7800,8 @@ abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
> 
> What version of qemu are you patching?  Line 7800 of current master is
> nowhere near 'case TARGET_NR_execve:' (line 7899)
> 
>>                  ret = -TARGET_E2BIG;
>>                  break;
>>              }
> 
> and current master has 'goto efault' rather than directly setting ret at
> this point.

Okay, I see that this context came from patch 1/2. Sorry for the noise
(I was trying to review this patch in isolation, since I've already
argued that 1/2 is probably not necessary).
Prasad Pandit March 6, 2017, 6:06 p.m. UTC | #4
+-- On Mon, 6 Mar 2017, Eric Blake wrote --+
| On 03/06/2017 01:17 AM, P J P wrote:
| > Arguments passed to execve(2) call from user program could
| > be large, allocating stack memory for them via alloca(3) call
| > would lead to bad behaviour. Use 'g_malloc0' to allocate memory
| > for such arguments.
| > 
| > Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
| > ---
| >  linux-user/syscall.c | 7 +++++--
| >  1 file changed, 5 insertions(+), 2 deletions(-)
| 
| Is this patch alone (without 1/2) sufficient to solve the problem?  If
| so, then drop 1/2.

  Yes, it seems to fix the issue. Still I think having ARG_MAX limit would be 
good, as system exec(3) routines too impose _SC_ARG_MAX limit. I'll send a 
revised patch with 'g_try_new' call instead of g_malloc0.

Thank you.
--
Prasad J Pandit / Red Hat Product Security Team
47AF CE69 3A90 54AA 9045 1053 DD13 3D32 FE5B 041F
Eric Blake March 6, 2017, 6:11 p.m. UTC | #5
On 03/06/2017 12:06 PM, P J P wrote:
> +-- On Mon, 6 Mar 2017, Eric Blake wrote --+
> | On 03/06/2017 01:17 AM, P J P wrote:
> | > Arguments passed to execve(2) call from user program could
> | > be large, allocating stack memory for them via alloca(3) call
> | > would lead to bad behaviour. Use 'g_malloc0' to allocate memory
> | > for such arguments.
> | > 
> | > Signed-off-by: Prasad J Pandit <pjp@fedoraproject.org>
> | > ---
> | >  linux-user/syscall.c | 7 +++++--
> | >  1 file changed, 5 insertions(+), 2 deletions(-)
> | 
> | Is this patch alone (without 1/2) sufficient to solve the problem?  If
> | so, then drop 1/2.
> 
>   Yes, it seems to fix the issue. Still I think having ARG_MAX limit would be 
> good, as system exec(3) routines too impose _SC_ARG_MAX limit. I'll send a 
> revised patch with 'g_try_new' call instead of g_malloc0.

If you impose any limit smaller than _SC_ARG_MAX, you are needlessly
limiting things.  Furthermore, _SC_ARG_MAX may not always be the same
value, depending on how the kernel was compiled.  So it's probably
asiest to just let execve() impose its own limits (and correctly report
errors to the caller when execve() fails), rather than trying to impose
limits yourself.

In short, the bug that you are fixing is not caused by the guest
requesting something beyond execve() limits, but caused by poor use of
alloca() leading to a stack overrun.  You only need to fix the bug (by
switching alloca() to heap allocation), not introduce additional ones
(by imposing arbitrary, and probably wrong, limits).
Prasad Pandit March 6, 2017, 6:43 p.m. UTC | #6
+-- On Mon, 6 Mar 2017, Eric Blake wrote --+
| If you impose any limit smaller than _SC_ARG_MAX, you are needlessly
| limiting things.  Furthermore, _SC_ARG_MAX may not always be the same
| value, depending on how the kernel was compiled.  So it's probably
| asiest to just let execve() impose its own limits (and correctly report
| errors to the caller when execve() fails), rather than trying to impose
| limits yourself.

  Okay.

| In short, the bug that you are fixing is not caused by the guest
| requesting something beyond execve() limits, but caused by poor use of
| alloca() leading to a stack overrun.

  Yes, true.

| You only need to fix the bug (by switching alloca() to heap allocation),

  Okay, I'll send just one revised patch.

Thank you.
--
Prasad J Pandit / Red Hat Product Security Team
47AF CE69 3A90 54AA 9045 1053 DD13 3D32 FE5B 041F
diff mbox

Patch

diff --git a/linux-user/syscall.c b/linux-user/syscall.c
index 86a4a9c..404fb0b 100644
--- a/linux-user/syscall.c
+++ b/linux-user/syscall.c
@@ -7800,8 +7800,8 @@  abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
                 ret = -TARGET_E2BIG;
                 break;
             }
-            argp = alloca((argc + 1) * sizeof(void *));
-            envp = alloca((envc + 1) * sizeof(void *));
+            argp = g_malloc0((argc + 1) * sizeof(void *));
+            envp = g_malloc0((envc + 1) * sizeof(void *));
 
             for (gp = guest_argp, q = argp; gp;
                   gp += sizeof(abi_ulong), q++) {
@@ -7862,6 +7862,9 @@  abi_long do_syscall(void *cpu_env, int num, abi_long arg1,
                     break;
                 unlock_user(*q, addr, 0);
             }
+
+            g_free(argp);
+            g_free(envp);
         }
         break;
     case TARGET_NR_chdir: