[patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()

Thomas Gleixner posted 6 patches 2 weeks, 1 day ago
[patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Thomas Gleixner 2 weeks, 1 day ago
When CONFIG_CPU_SPECTRE=n then get_user() is missing the 8 byte ASM variant
for no real good reason. This prevents using get_user(u64) in generic code.

Implement it as a sequence of two 4-byte reads with LE/BE awareness.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Russell King <linux@armlinux.org.uk>
Cc: linux-arm-kernel@lists.infradead.org
Closes: https://lore.kernel.org/oe-kbuild-all/202509120155.pFgwfeUD-lkp@intel.com/
---
V2: New patch to fix the 0-day fallout
---
 arch/arm/include/asm/uaccess.h |   17 +++++++++++++++++
 1 file changed, 17 insertions(+)

--- a/arch/arm/include/asm/uaccess.h
+++ b/arch/arm/include/asm/uaccess.h
@@ -295,6 +295,7 @@ do {									\
 	case 1:	__get_user_asm_byte(__gu_val, __gu_addr, err, __t); break;	\
 	case 2:	__get_user_asm_half(__gu_val, __gu_addr, err, __t); break;	\
 	case 4:	__get_user_asm_word(__gu_val, __gu_addr, err, __t); break;	\
+	case 8:	__get_user_asm_dword(__gu_val, __gu_addr, err, __t); break;	\
 	default: (__gu_val) = __get_user_bad();				\
 	}								\
 	uaccess_restore(__ua_flags);					\
@@ -353,6 +354,22 @@ do {									\
 #define __get_user_asm_word(x, addr, err, __t)			\
 	__get_user_asm(x, addr, err, "ldr" __t)
 
+#ifdef __ARMEB__
+#define __WORD0_OFFS	4
+#define __WORD1_OFFS	0
+#else
+#define __WORD0_OFFS	0
+#define __WORD1_OFFS	4
+#endif
+
+#define __get_user_asm_dword(x, addr, err, __t)				\
+	({								\
+	unsigned long __w0, __w1;					\
+	__get_user_asm(__w0, addr + __WORD0_OFFS, err, "ldr" __t);	\
+	__get_user_asm(__w1, addr + __WORD1_OFFS, err, "ldr" __t);	\
+	(x) = ((u64)__w1 << 32) | (u64) __w0;				\
+})
+
 #define __put_user_switch(x, ptr, __err, __fn)				\
 	do {								\
 		const __typeof__(*(ptr)) __user *__pu_ptr = (ptr);	\
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Russell King (Oracle) 2 weeks, 1 day ago
On Tue, Sep 16, 2025 at 06:33:09PM +0200, Thomas Gleixner wrote:
> When CONFIG_CPU_SPECTRE=n then get_user() is missing the 8 byte ASM variant
> for no real good reason. This prevents using get_user(u64) in generic code.

I'm sure you will eventually discover the reason when you start getting
all the kernel build bot warnings that will result from a cast from a
u64 to a pointer.

You're not the first to try implementing this.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Thomas Gleixner 2 weeks, 1 day ago
On Tue, Sep 16 2025 at 22:26, Russell King wrote:
> On Tue, Sep 16, 2025 at 06:33:09PM +0200, Thomas Gleixner wrote:
>> When CONFIG_CPU_SPECTRE=n then get_user() is missing the 8 byte ASM variant
>> for no real good reason. This prevents using get_user(u64) in generic code.
>
> I'm sure you will eventually discover the reason when you start getting
> all the kernel build bot warnings that will result from a cast from a
> u64 to a pointer.

I really don't know which cast you are talking about.

  	u64 __user *uaddr =  ...;
	u64 val;

        ....
        unsafe_get_user(val, uaddr, fault);

The only casts in this macro maze are in __get_user_err():

 1) Casting the uaddr pointer to unsigned long:

     unsigned long __gu_addr = (unsigned long)(ptr);

    which is correct because a *u64 pointer is still only 32bit wide on a
    32bit machine, no?

 2) Casting the result:

    (x) = (__typeof__(*(ptr)))__gu_val;

    which is casting to the type to which the pointer points to,
    i.e. u64 in this case.

I definitely checked the ASM result after I successfully compiled the
above w/o warnings. It compiles to:

 ad0:	ee032f10 	mcr	15, 0, r2, cr3, cr0, {0}
 ad4:	e3a00000 	mov	r0, #0
 ad8:	e4b3e000 	ldrt	lr, [r3], #0
 adc:	e2833004 	add	r3, r3, #4
 ae0:	e4b32000 	ldrt	r2, [r3], #0
 ae4:	ee03cf10 	mcr	15, 0, ip, cr3, cr0, {0}
 ae8:	e16f0f10 	clz	r0, r0
 aec:	e581e000 	str	lr, [r1]
 af0:	e5812004 	str	r2, [r1, #4]

which is magically correct despite the fact that I missed to change the
type of __gu_val to 'unsigned long long'. I just noticed when I tried to
figure out which cast you were referring to.

The wonderful and surprising world of macro preprocessing. :)

That unsigned long long is not hurtful as the compiler is smart enough
to optimize it away when __get_user_err() is invoked to read an u8 from
user:

 b18:	ee033f10 	mcr	15, 0, r3, cr3, cr0, {0}
 b1c:	e3a03000 	mov	r3, #0
 b20:	e4f04000 	ldrbt	r4, [r0], #0
 b24:	ee032f10 	mcr	15, 0, r2, cr3, cr0, {0}
 b28:	e16f0f13 	clz	r0, r3
 b2c:	e5c14000 	strb	r4, [r1]

which is exactly the same result as before this change.

Thanks,

        tglx

--- a/arch/arm/include/asm/uaccess.h
+++ b/arch/arm/include/asm/uaccess.h
@@ -286,7 +286,7 @@ extern int __put_user_8(void *, unsigned
 #define __get_user_err(x, ptr, err, __t)				\
 do {									\
 	unsigned long __gu_addr = (unsigned long)(ptr);			\
-	unsigned long __gu_val;						\
+	unsigned long long __gu_val;					\
 	unsigned int __ua_flags;					\
 	__chk_user_ptr(ptr);						\
 	might_fault();							\
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Russell King (Oracle) 2 weeks, 1 day ago
On Wed, Sep 17, 2025 at 07:48:00AM +0200, Thomas Gleixner wrote:
> On Tue, Sep 16 2025 at 22:26, Russell King wrote:
> > On Tue, Sep 16, 2025 at 06:33:09PM +0200, Thomas Gleixner wrote:
> >> When CONFIG_CPU_SPECTRE=n then get_user() is missing the 8 byte ASM variant
> >> for no real good reason. This prevents using get_user(u64) in generic code.
> >
> > I'm sure you will eventually discover the reason when you start getting
> > all the kernel build bot warnings that will result from a cast from a
> > u64 to a pointer.
> 
> I really don't know which cast you are talking about.

I'll grant you that the problem is not obvious. It comes about because
of all the different types that get_user() is subject to - it's not
just integers, it's also pointers.

The next bit to realise is that casting between integers that are not
the same size as a pointer causes warnings. For example, casting
between a 64-bit integer type and pointer type causes the compiler to
emit a warning. It doesn't matter if the code path ends up being
optimised away, the warning is still issued.

Putting together a simple test case, where the only change is making
__gu_val an unsigned long long:

t.c: In function ‘get_ptr’:
t.c:40:15: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
   40 |         (x) = (__typeof__(*(ptr)))__gu_val;                             \
      |               ^
t.c:21:9: note: in expansion of macro ‘__get_user_err’
   21 |         __get_user_err((x), (ptr), __gu_err, TUSER());                  \
      |         ^~~~~~~~~~~~~~
t.c:102:16: note: in expansion of macro ‘__get_user’
  102 |         return __get_user(p, ptr);
      |                ^~~~~~~~~~

In order for the code you are modifying to be reachable, you need to
build with CONFIG_CPU_SPECTRE disabled. This is produced by:

int get_ptr(void **ptr)
{
        void *p;

        return __get_user(p, ptr);
}

Now, one idea may be to declare __gu_val as:

	__typeof__(x) __gu_val;

but then we run into:

t.c: In function ‘get_ptr’:
t.c:37:29: error: assignment to ‘void *’ from ‘int’ makes pointer from integer without a cast [-Wint-conversion]
   37 |         default: (__gu_val) = __get_user_bad();                         \
      |                             ^
t.c:21:9: note: in expansion of macro ‘__get_user_err’
   21 |         __get_user_err((x), (ptr), __gu_err, TUSER());                  \
      |         ^~~~~~~~~~~~~~
t.c:102:16: note: in expansion of macro ‘__get_user’
  102 |         return __get_user(p, ptr);
      |                ^~~~~~~~~~

You may think this is easy to solve, just change the last cast to:

	(x) = (__typeof__(*(ptr)))(__typeof__(x))__gu_val;

but that doesn't work either (because in the test case __typeof__(x) is
still a pointer type. You can't cast this down to a 32-bit quantity
because that will knock off the upper 32 bits for the case you're trying
to add.

You may think, why not  move this cast into each switch statement...
there will still be warnings because the cast is still reachable at the
point the compiler evaluates the code for warnings, even though the
optimiser gets rid of it later.

Feel free to try to solve this, but I can assure you that you certainly
are not the first. Several people have already tried.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Thomas Gleixner 2 weeks ago
On Wed, Sep 17 2025 at 10:41, Russell King wrote:
> On Wed, Sep 17, 2025 at 07:48:00AM +0200, Thomas Gleixner wrote:
>
> Putting together a simple test case, where the only change is making
> __gu_val an unsigned long long:
>
> t.c: In function ‘get_ptr’:
> t.c:40:15: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
>    40 |         (x) = (__typeof__(*(ptr)))__gu_val;                             \
>       |               ^
> t.c:21:9: note: in expansion of macro ‘__get_user_err’
>    21 |         __get_user_err((x), (ptr), __gu_err, TUSER());                  \
>       |         ^~~~~~~~~~~~~~
> t.c:102:16: note: in expansion of macro ‘__get_user’
>   102 |         return __get_user(p, ptr);
>       |                ^~~~~~~~~~
>
> In order for the code you are modifying to be reachable, you need to
> build with CONFIG_CPU_SPECTRE disabled. This is produced by:
>
> int get_ptr(void **ptr)
> {
>         void *p;
>
>         return __get_user(p, ptr);
> }

Duh, yes. I hate get_user() and I did not notice, because the
allmodconfig build breaks early due to frame size checks, so I was too
lazy to find that config knob and built only a couple of things and an
artificial test case for u64.

But it actually can be solved solvable by switching the casting to:

    (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;

Not pretty, but after upping the frame size limit it builds an
allmodconfig kernel.

The proper thing to use the corresponding sized values within the case
$SIZE sections i.e.:

	size 1: {
       		u8 __gu_val;
                __get_user_asm_byte(__gu_val, __gu_addr, err, __t);
                (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;
		break;
	}
        ...

See below.

> Feel free to try to solve this, but I can assure you that you certainly
> are not the first. Several people have already tried.

Obviously not hard enough. :)

Thanks,

        tglx
---
Subject: ARM: uaccess: Implement missing __get_user_asm_dword()
From: Thomas Gleixner <tglx@linutronix.de>
Date: Fri, 12 Sep 2025 20:16:11 +0200

When CONFIG_CPU_SPECTRE=n then get_user() is missing the 8 byte ASM variant
for no real good reason. This prevents using get_user(u64) in generic code.

Implement it as a sequence of two 4-byte reads with LE/BE awareness and
fixup the size switch case to make get_user(*ptr, **usrptr) work.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Russell King <linux@armlinux.org.uk>
Cc: linux-arm-kernel@lists.infradead.org
Closes: https://lore.kernel.org/oe-kbuild-all/202509120155.pFgwfeUD-lkp@intel.com/
---
V2a: Solve the *ptr issue vs. unsigned long long - Russell
V2: New patch to fix the 0-day fallout
---
 arch/arm/include/asm/uaccess.h |   50 ++++++++++++++++++++++++++++++++++++-----
 1 file changed, 44 insertions(+), 6 deletions(-)

--- a/arch/arm/include/asm/uaccess.h
+++ b/arch/arm/include/asm/uaccess.h
@@ -286,19 +286,41 @@ extern int __put_user_8(void *, unsigned
 #define __get_user_err(x, ptr, err, __t)				\
 do {									\
 	unsigned long __gu_addr = (unsigned long)(ptr);			\
-	unsigned long __gu_val;						\
 	unsigned int __ua_flags;					\
 	__chk_user_ptr(ptr);						\
 	might_fault();							\
 	__ua_flags = uaccess_save_and_enable();				\
 	switch (sizeof(*(ptr))) {					\
-	case 1:	__get_user_asm_byte(__gu_val, __gu_addr, err, __t); break;	\
-	case 2:	__get_user_asm_half(__gu_val, __gu_addr, err, __t); break;	\
-	case 4:	__get_user_asm_word(__gu_val, __gu_addr, err, __t); break;	\
-	default: (__gu_val) = __get_user_bad();				\
+	case 1:	{							\
+		u8 __gu_val;						\
+		__get_user_asm_byte(__gu_val, __gu_addr, err, __t);	\
+		(x) = *(__force __typeof__(*(ptr)) *) &__gu_val;	\
+		break;							\
+	}								\
+	case 2:	{							\
+		u16 __gu_val;						\
+		__get_user_asm_half(__gu_val, __gu_addr, err, __t);	\
+		(x) = *(__force __typeof__(*(ptr)) *) &__gu_val;	\
+		break;							\
+	}								\
+	case 4:	{							\
+		u32 __gu_val;						\
+		__get_user_asm_word(__gu_val, __gu_addr, err, __t);	\
+		(x) = *(__force __typeof__(*(ptr)) *) &__gu_val;	\
+		break;							\
+	}								\
+	case 8:	{							\
+		u64 __gu_val;						\
+		__get_user_asm_dword(__gu_val, __gu_addr, err, __t);	\
+		(x) = *(__force __typeof__(*(ptr)) *) &__gu_val;	\
+		break;							\
+	}								\
+	default: {							\
+		unsigned long __gu_val;					\
+		(__gu_val) = __get_user_bad();				\
+	}								\
 	}								\
 	uaccess_restore(__ua_flags);					\
-	(x) = (__typeof__(*(ptr)))__gu_val;				\
 } while (0)
 #endif
 
@@ -353,6 +375,22 @@ do {									\
 #define __get_user_asm_word(x, addr, err, __t)			\
 	__get_user_asm(x, addr, err, "ldr" __t)
 
+#ifdef __ARMEB__
+#define __WORD0_OFFS	4
+#define __WORD1_OFFS	0
+#else
+#define __WORD0_OFFS	0
+#define __WORD1_OFFS	4
+#endif
+
+#define __get_user_asm_dword(x, addr, err, __t)				\
+	({								\
+	unsigned long __w0, __w1;					\
+	__get_user_asm(__w0, addr + __WORD0_OFFS, err, "ldr" __t);	\
+	__get_user_asm(__w1, addr + __WORD1_OFFS, err, "ldr" __t);	\
+	(x) = ((u64)__w1 << 32) | (u64) __w0;				\
+})
+
 #define __put_user_switch(x, ptr, __err, __fn)				\
 	do {								\
 		const __typeof__(*(ptr)) __user *__pu_ptr = (ptr);	\
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Russell King (Oracle) 2 weeks ago
On Wed, Sep 17, 2025 at 03:55:10PM +0200, Thomas Gleixner wrote:
> On Wed, Sep 17 2025 at 10:41, Russell King wrote:
> > On Wed, Sep 17, 2025 at 07:48:00AM +0200, Thomas Gleixner wrote:
> >
> > Putting together a simple test case, where the only change is making
> > __gu_val an unsigned long long:
> >
> > t.c: In function ‘get_ptr’:
> > t.c:40:15: warning: cast to pointer from integer of different size [-Wint-to-pointer-cast]
> >    40 |         (x) = (__typeof__(*(ptr)))__gu_val;                             \
> >       |               ^
> > t.c:21:9: note: in expansion of macro ‘__get_user_err’
> >    21 |         __get_user_err((x), (ptr), __gu_err, TUSER());                  \
> >       |         ^~~~~~~~~~~~~~
> > t.c:102:16: note: in expansion of macro ‘__get_user’
> >   102 |         return __get_user(p, ptr);
> >       |                ^~~~~~~~~~
> >
> > In order for the code you are modifying to be reachable, you need to
> > build with CONFIG_CPU_SPECTRE disabled. This is produced by:
> >
> > int get_ptr(void **ptr)
> > {
> >         void *p;
> >
> >         return __get_user(p, ptr);
> > }
> 
> Duh, yes. I hate get_user() and I did not notice, because the
> allmodconfig build breaks early due to frame size checks, so I was too
> lazy to find that config knob and built only a couple of things and an
> artificial test case for u64.
> 
> But it actually can be solved solvable by switching the casting to:
> 
>     (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;
> 
> Not pretty, but after upping the frame size limit it builds an
> allmodconfig kernel.

For me, this produces:

get-user-test.c:41:16: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
   41 |         (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;                \
      |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

with arm-linux-gnueabihf-gcc (Debian 14.2.0-19) 14.2.0

Maybe you're using a different compiler that doesn't issue that warning?

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Thomas Gleixner 2 weeks ago
On Wed, Sep 17 2025 at 16:17, Russell King wrote:
> On Wed, Sep 17, 2025 at 03:55:10PM +0200, Thomas Gleixner wrote:
>> But it actually can be solved solvable by switching the casting to:
>> 
>>     (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;
>> 
>> Not pretty, but after upping the frame size limit it builds an
>> allmodconfig kernel.
>
> For me, this produces:
>
> get-user-test.c:41:16: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
>    41 |         (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;                \
>       |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>
> with arm-linux-gnueabihf-gcc (Debian 14.2.0-19) 14.2.0
>
> Maybe you're using a different compiler that doesn't issue that
> warning?

Yes :)

Was this with the one-line change or with the full conversion?

Thanks,

        tglx
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Nathan Chancellor 2 weeks ago
On Wed, Sep 17, 2025 at 04:17:38PM +0100, Russell King (Oracle) wrote:
> For me, this produces:
> 
> get-user-test.c:41:16: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
>    41 |         (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;                \
>       |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> 
> with arm-linux-gnueabihf-gcc (Debian 14.2.0-19) 14.2.0
> 
> Maybe you're using a different compiler that doesn't issue that warning?

Maybe because the kernel uses -fno-strict-aliasing, which presumably
turns off -Wstrict-aliasing?

Cheers,
Nathan
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Russell King (Oracle) 2 weeks ago
On Wed, Sep 17, 2025 at 10:14:24AM -0700, Nathan Chancellor wrote:
> On Wed, Sep 17, 2025 at 04:17:38PM +0100, Russell King (Oracle) wrote:
> > For me, this produces:
> > 
> > get-user-test.c:41:16: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
> >    41 |         (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;                \
> >       |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
> > 
> > with arm-linux-gnueabihf-gcc (Debian 14.2.0-19) 14.2.0
> > 
> > Maybe you're using a different compiler that doesn't issue that warning?
> 
> Maybe because the kernel uses -fno-strict-aliasing, which presumably
> turns off -Wstrict-aliasing?

Thanks, I'd forgotten to pick the -f flags for building the out of tree
test. Yes, that does work, but I wonder whether the powerpc 32-bit
approach with __long_type() that Christophe mentioned would be better.
That also appears to avoid all issues, and doesn't need the use of
the nasty __force, address-of and deref trick.

-- 
RMK's Patch system: https://www.armlinux.org.uk/developer/patches/
FTTP is here! 80Mbps down 10Mbps up. Decent connectivity at last!
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Thomas Gleixner 2 weeks ago
On Wed, Sep 17 2025 at 18:34, Russell King wrote:

> On Wed, Sep 17, 2025 at 10:14:24AM -0700, Nathan Chancellor wrote:
>> On Wed, Sep 17, 2025 at 04:17:38PM +0100, Russell King (Oracle) wrote:
>> > For me, this produces:
>> > 
>> > get-user-test.c:41:16: warning: dereferencing type-punned pointer will break strict-aliasing rules [-Wstrict-aliasing]
>> >    41 |         (x) = *(__force __typeof__(*(ptr)) *) &__gu_val;                \
>> >       |                ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> > 
>> > with arm-linux-gnueabihf-gcc (Debian 14.2.0-19) 14.2.0
>> > 
>> > Maybe you're using a different compiler that doesn't issue that warning?
>> 
>> Maybe because the kernel uses -fno-strict-aliasing, which presumably
>> turns off -Wstrict-aliasing?
>
> Thanks, I'd forgotten to pick the -f flags for building the out of tree
> test. Yes, that does work, but I wonder whether the powerpc 32-bit
> approach with __long_type() that Christophe mentioned would be better.
> That also appears to avoid all issues, and doesn't need the use of
> the nasty __force, address-of and deref trick.

Hmm. I just noticed that include/asm-generic/uaccess.h does exactly the
same what I did with the per size case variables and that seems to be
not subject to endless bot complaints either.

But sure, the PPC trick is neat too. No strong preference, just that I'm
leaning towards the per case split up as it's less obfuscated IMO.

Thanks,

        tglx
Re: [patch V2 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Christophe Leroy 2 weeks, 1 day ago

Le 17/09/2025 à 11:41, Russell King (Oracle) a écrit :
> You may think this is easy to solve, just change the last cast to:
> 
>          (x) = (__typeof__(*(ptr)))(__typeof__(x))__gu_val;
> 
> but that doesn't work either (because in the test case __typeof__(x) is
> still a pointer type. You can't cast this down to a 32-bit quantity
> because that will knock off the upper 32 bits for the case you're trying
> to add.
> 
> You may think, why not  move this cast into each switch statement...
> there will still be warnings because the cast is still reachable at the
> point the compiler evaluates the code for warnings, even though the
> optimiser gets rid of it later.
> 
> Feel free to try to solve this, but I can assure you that you certainly
> are not the first. Several people have already tried.
> 

No such problem on powerpc/32, maybe because we have defined and use 
macro __long_type(x), see below:

#define __get_user_size_allowed(x, ptr, size, retval)		\
do {								\
	retval = 0;						\
	BUILD_BUG_ON(size > sizeof(x));				\
	switch (size) {						\
	case 1: __get_user_asm(x, (u8 __user *)ptr, retval, "lbz"); break;	\
	case 2: __get_user_asm(x, (u16 __user *)ptr, retval, "lhz"); break;	\
	case 4: __get_user_asm(x, (u32 __user *)ptr, retval, "lwz"); break;	\
	case 8: __get_user_asm2(x, (u64 __user *)ptr, retval);  break;	\
	default: x = 0; BUILD_BUG();				\
	}							\
} while (0)

/*
  * This is a type: either unsigned long, if the argument fits into
  * that type, or otherwise unsigned long long.
  */
#define __long_type(x) \
	__typeof__(__builtin_choose_expr(sizeof(x) > sizeof(0UL), 0ULL, 0UL))

#define __get_user(x, ptr)					\
({								\
	long __gu_err;						\
	__long_type(*(ptr)) __gu_val;				\
	__typeof__(*(ptr)) __user *__gu_addr = (ptr);	\
	__typeof__(sizeof(*(ptr))) __gu_size = sizeof(*(ptr));	\
								\
	might_fault();					\
	allow_read_from_user(__gu_addr, __gu_size);		\
	__get_user_size_allowed(__gu_val, __gu_addr, __gu_size, __gu_err);	\
	prevent_read_from_user(__gu_addr, __gu_size);		\
	(x) = (__typeof__(*(ptr)))__gu_val;			\
								\
	__gu_err;						\
})


[patch V2a 1/6] ARM: uaccess: Implement missing __get_user_asm_dword()
Posted by Thomas Gleixner 1 week, 5 days ago
When CONFIG_CPU_SPECTRE=n then get_user() is missing the 8 byte ASM
variant.  This prevents using get_user(u64) in generic code.

Implement it as a sequence of two 4-byte reads with LE/BE awareness and
make the data type for the intermediate variable to read into dependend
on the target type. For 8,16,32 bit use unsigned long and for 64 bit
unsigned long long

The __long_type() macro and the idea to solve it was lifted from
PowerPC. Thanks to Christophe for pointing it out.

Reported-by: kernel test robot <lkp@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Russell King <linux@armlinux.org.uk>
Cc: linux-arm-kernel@lists.infradead.org
Closes: https://lore.kernel.org/oe-kbuild-all/202509120155.pFgwfeUD-lkp@intel.com/
---
V2a: Solve the *ptr issue vs. unsigned long long - Russell/Christophe
V2: New patch to fix the 0-day fallout
---
 arch/arm/include/asm/uaccess.h |   26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

--- a/arch/arm/include/asm/uaccess.h
+++ b/arch/arm/include/asm/uaccess.h
@@ -283,10 +283,17 @@ extern int __put_user_8(void *, unsigned
 	__gu_err;							\
 })
 
+/*
+ * This is a type: either unsigned long, if the argument fits into
+ * that type, or otherwise unsigned long long.
+ */
+#define __long_type(x) \
+	__typeof__(__builtin_choose_expr(sizeof(x) > sizeof(0UL), 0ULL, 0UL))
+
 #define __get_user_err(x, ptr, err, __t)				\
 do {									\
 	unsigned long __gu_addr = (unsigned long)(ptr);			\
-	unsigned long __gu_val;						\
+	__long_type(x) __gu_val;					\
 	unsigned int __ua_flags;					\
 	__chk_user_ptr(ptr);						\
 	might_fault();							\
@@ -295,6 +302,7 @@ do {									\
 	case 1:	__get_user_asm_byte(__gu_val, __gu_addr, err, __t); break;	\
 	case 2:	__get_user_asm_half(__gu_val, __gu_addr, err, __t); break;	\
 	case 4:	__get_user_asm_word(__gu_val, __gu_addr, err, __t); break;	\
+	case 8:	__get_user_asm_dword(__gu_val, __gu_addr, err, __t); break;	\
 	default: (__gu_val) = __get_user_bad();				\
 	}								\
 	uaccess_restore(__ua_flags);					\
@@ -353,6 +361,22 @@ do {									\
 #define __get_user_asm_word(x, addr, err, __t)			\
 	__get_user_asm(x, addr, err, "ldr" __t)
 
+#ifdef __ARMEB__
+#define __WORD0_OFFS	4
+#define __WORD1_OFFS	0
+#else
+#define __WORD0_OFFS	0
+#define __WORD1_OFFS	4
+#endif
+
+#define __get_user_asm_dword(x, addr, err, __t)				\
+	({								\
+	unsigned long __w0, __w1;					\
+	__get_user_asm(__w0, addr + __WORD0_OFFS, err, "ldr" __t);	\
+	__get_user_asm(__w1, addr + __WORD1_OFFS, err, "ldr" __t);	\
+	(x) = ((u64)__w1 << 32) | (u64) __w0;				\
+})
+
 #define __put_user_switch(x, ptr, __err, __fn)				\
 	do {								\
 		const __typeof__(*(ptr)) __user *__pu_ptr = (ptr);	\