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

Subsystems: linux for powerpc (32-bit and 64-bit), the rest

STALE796d

8 messages, 4 authors, 2024-05-31 · open the first message on its own page

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

From: Breno Leitao <leitao@debian.org>
Date: 2018-08-21 18:42:15

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);

Signed-off-by: Breno Leitao <leitao@debian.org>
---
 arch/powerpc/kernel/rtas.c | 6 ++++--
 1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/arch/powerpc/kernel/rtas.c b/arch/powerpc/kernel/rtas.c
index 8afd146bc9c7..5ef3c863003d 100644
--- a/arch/powerpc/kernel/rtas.c
+++ b/arch/powerpc/kernel/rtas.c
@@ -27,6 +27,7 @@
 #include <linux/slab.h>
 #include <linux/reboot.h>
 #include <linux/syscalls.h>
+#include <linux/nospec.h>
 
 #include <asm/prom.h>
 #include <asm/rtas.h>
@@ -1056,7 +1057,7 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
 	struct rtas_args args;
 	unsigned long flags;
 	char *buff_copy, *errbuf = NULL;
-	int nargs, nret, token;
+	int index, nargs, nret, token;
 
 	if (!capable(CAP_SYS_ADMIN))
 		return -EPERM;
@@ -1084,7 +1085,8 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
 	if (token == RTAS_UNKNOWN_SERVICE)
 		return -EINVAL;
 
-	args.rets = &args.args[nargs];
+	index = array_index_nospec(nargs, ARRAY_SIZE(args.args));
+	args.rets = &args.args[index];
 	memset(args.rets, 0, nret * sizeof(rtas_arg_t));
 
 	/* Need to handle ibm,suspend_me call specially */
-- 
2.16.3

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

From: Christophe Leroy <hidden>
Date: 2024-03-12 08:18:51

+Nathan as this is RTAS related.

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 ?

Thanks
Christophe

quoted hunk
Signed-off-by: Breno Leitao <leitao@debian.org>
---
  arch/powerpc/kernel/rtas.c | 6 ++++--
  1 file changed, 4 insertions(+), 2 deletions(-)
diff --git a/arch/powerpc/kernel/rtas.c b/arch/powerpc/kernel/rtas.c
index 8afd146bc9c7..5ef3c863003d 100644
--- a/arch/powerpc/kernel/rtas.c
+++ b/arch/powerpc/kernel/rtas.c
@@ -27,6 +27,7 @@
  #include <linux/slab.h>
  #include <linux/reboot.h>
  #include <linux/syscalls.h>
+#include <linux/nospec.h>
  
  #include <asm/prom.h>
  #include <asm/rtas.h>
@@ -1056,7 +1057,7 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
  	struct rtas_args args;
  	unsigned long flags;
  	char *buff_copy, *errbuf = NULL;
-	int nargs, nret, token;
+	int index, nargs, nret, token;
  
  	if (!capable(CAP_SYS_ADMIN))
  		return -EPERM;
@@ -1084,7 +1085,8 @@ SYSCALL_DEFINE1(rtas, struct rtas_args __user *, uargs)
  	if (token == RTAS_UNKNOWN_SERVICE)
  		return -EINVAL;
  
-	args.rets = &args.args[nargs];
+	index = array_index_nospec(nargs, ARRAY_SIZE(args.args));
+	args.rets = &args.args[index];
  	memset(args.rets, 0, nret * sizeof(rtas_arg_t));
  
  	/* Need to handle ibm,suspend_me call specially */

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

From: Breno Leitao <leitao@debian.org>
Date: 2024-03-12 09:06:19

On Tue, Mar 12, 2024 at 08:17:42AM +0000, Christophe Leroy wrote:
+Nathan as this is RTAS related.

Le 21/08/2018 à 20:42, Breno Leitao a écrit :
quoted
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.

Thanks for revamping this one.

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

From: Michael Ellerman <mpe@ellerman.id.au>
Date: 2024-03-12 11:08:36

Breno Leitao [off-list ref] writes:
On Tue, Mar 12, 2024 at 08:17:42AM +0000, Christophe Leroy wrote:
quoted
+Nathan as this is RTAS related.

Le 21/08/2018 à 20:42, Breno Leitao a écrit :
quoted
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. I thought I'd talked to you on Slack
about it, but maybe I didn't.

Anyway we should probably just fix it to be safe and keep the static
checkers happy.

I'll rebase it and apply it, I'm sure you've got better things to do :)

cheers

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

From: Breno Leitao <leitao@debian.org>
Date: 2024-03-12 13:11:25

On Tue, Mar 12, 2024 at 10:07:54PM +1100, Michael Ellerman wrote:
Breno Leitao [off-list ref] writes:
quoted
On Tue, Mar 12, 2024 at 08:17:42AM +0000, Christophe Leroy wrote:
quoted
+Nathan as this is RTAS related.

Le 21/08/2018 à 20:42, Breno Leitao a écrit :
quoted
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. I thought I'd talked to you on Slack
about it, but maybe I didn't.

Anyway we should probably just fix it to be safe and keep the static
checkers happy.

I'll rebase it and apply it, I'm sure you've got better things to do :)
Awesome. Thanks Michael!

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

From: Nathan Lynch <hidden>
Date: 2024-03-18 15:27:05

Michael Ellerman [off-list ref] writes:
Breno Leitao [off-list ref] writes:
quoted
On Tue, Mar 12, 2024 at 08:17:42AM +0000, Christophe Leroy wrote:
quoted
+Nathan as this is RTAS related.
Thanks!
quoted
quoted
Le 21/08/2018 à 20:42, Breno Leitao a écrit :
quoted
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?
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()?

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).

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

From: Michael Ellerman <mpe@ellerman.id.au>
Date: 2024-05-21 01:31:36

Nathan Lynch [off-list ref] writes:
Michael Ellerman [off-list ref] writes:
quoted
Breno Leitao [off-list ref] writes:
quoted
On Tue, Mar 12, 2024 at 08:17:42AM +0000, Christophe Leroy wrote:
quoted
+Nathan as this is RTAS related.
Thanks!
quoted
quoted
quoted
Le 21/08/2018 à 20:42, Breno Leitao a écrit :
quoted
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.
quoted
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

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

From: Nathan Lynch <hidden>
Date: 2024-05-31 00:36:17

Michael Ellerman [off-list ref] writes:
Nathan Lynch [off-list ref] writes:
quoted
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.
quoted
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.
Agreed. I'm sending a new patch that does this.
token is different, at least in the above code, because it's not bounds
checked, so there's no bounds check to bypass.
Right.
Though maybe there is one inside the rtas lookup code that should be
masked.
In rtas_function_token()? I think it's OK... the handles passed to it
are always build-time constants that are supposed to be within the
bounds of the function table.
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help