Subject: Re: [PATCH] powerpc/kernel: Fix potential spectre v1 in syscall

From: Michael Ellerman <[email-masked]>
Date: Tue, 21 May 2024 11:23:38 +1000

Nathan Lynch <[email-masked]> writes:
> Michael Ellerman <[email-masked]> writes:
>> Breno Leitao <[email-masked]> writes:
>>> On Tue, Mar 12, 2024 at 08:17:42AM +0000, Christophe Leroy wrote:
>>>> +Nathan as this is RTAS related.
>
> Thanks!
>
>>>> Le 21/08/2018 à 20:42, Breno Leitao a écrit :
>>>> > The rtas syscall reads a value from a user-provided structure and uses it
>>>> > to index an array, being a possible area for a potential spectre v1 attack.
>>>> > This is the code that exposes this problem.
>>>> > 
>>>> > 	args.rets = &args.args[nargs];
>>>> > 
>>>> > The nargs is an user provided value, and the below code is an example where
>>>> > the 'nargs' value would be set to XX.
>>>> > 
>>>> > 	struct rtas_args ra;
>>>> > 	ra.nargs = htobe32(XX);
>>>> > 	syscall(__NR_rtas, &ra);
>>>> 
>>>> 
>>>> This patch has been hanging around in patchwork since 2018 and doesn't 
>>>> apply anymore. Is it still relevant ? If so, can you rebase et resubmit ?
>>>
>>> This seems to be important, since nargs is a user-provided value. I can
>>> submit it if the maintainers are willing to accept. I do not want to
>>> spend my time if no one is willing to review it.
>>
>> My memory is that I didn't think it was actually a problem, because all
>> we do is memset args.rets to zero.
>
> This is also my initial reaction to this. I suppose if the memset()
> implementation performs some validation of the destination buffer
> contents (comparing to a known poison value or something) that could
> load the CPU cache then there is a more plausible issue?

Yeah I guess that's possible.

In the past my approach to these was to analyse the exploitability of
each case and only patch those where there was a feasible case to be
made.

But I think that was wrong, doing that analysis is too time consuming at
scale, is easy to get wrong, and is also fragile in the face of the code
changing.

Especially in cases like this where the performance cost of the checks
is going to be dwarfed by other factors like syscall/firmware overhead.

>> Anyway we should probably just fix it to be safe and keep the static
>> checkers happy.
>
> Here is the relevant passage in its current state:
>
>         if (copy_from_user(&args, uargs, 3 * sizeof(u32)) != 0)
>                 return -EFAULT;
>
>         nargs = be32_to_cpu(args.nargs);
>         nret  = be32_to_cpu(args.nret);
>         token = be32_to_cpu(args.token);
>
>         if (nargs >= ARRAY_SIZE(args.args)
>             || nret > ARRAY_SIZE(args.args)
>             || nargs + nret > ARRAY_SIZE(args.args))
>                 return -EINVAL;
>
>         /* Copy in args. */
>         if (copy_from_user(args.args, uargs->args,
>                            nargs * sizeof(rtas_arg_t)) != 0)
>                 return -EFAULT;
>
>         /*
>          * If this token doesn't correspond to a function the kernel
>          * understands, you're not allowed to call it.
>          */
>         func = rtas_token_to_function_untrusted(token);
>         if (!func)
>                 return -EINVAL;
>
>         args.rets = &args.args[nargs];
>         memset(args.rets, 0, nret * sizeof(rtas_arg_t));
>
> Some questions:
>
> 1. The patch sanitizes 'nargs' immediately before the call to memset(),
>    but shouldn't that happen before 'nargs' is used as an input to
>    copy_from_user()?

I think the reasoning is that there's no way to exploit an out of bounds
value using copy_from_user(). But it's much easier to reason about if we
just do the sanitisation up front.

> 2. If 'nargs' needs this treatment, then why wouldn't the user-supplied
>    'nret' and 'token' need them as well? 'nret' is used to index the
>    same array as 'nargs'. And at least conceptually, 'token' is used to
>    index a data structure (xarray) with array-like semantics (to be
>    fair, this is a relatively recent development and was not the case
>    when this change was submitted).
    
I don't know exactly what smatch looks for when trying to detect these,
but I suspect it's a plain array access. Not sure why it doesn't
complain about nret, but I think it would be good to sanitise it as well.

token is different, at least in the above code, because it's not bounds
checked, so there's no bounds check to bypass. Though maybe there is one
inside the rtas lookup code that should be masked.

cheers