* [PATCH 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
@ 2026-10-19 1:58 ` Qiliang Yuan
0 siblings, 0 replies; 14+ messages in thread
From: Qiliang Yuan @ 2026-10-19 1:58 UTC (permalink / raw)
To: Russell King, Catalin Marinas, Will Deacon, Mark Rutland,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Ritesh Harjani (IBM),
Shrikanth Hegde, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Shuah Khan, Marco Elver, Andrey Ryabinin, Alexander Potapenko,
Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Andrew Morton
Cc: linux-arm-kernel, Kees Cook, linux-kernel, linux-hardening,
linuxppc-dev, linux-riscv, linux-s390, linux-kselftest, kasan-dev,
linux-mm, Qiliang Yuan, Peter Collingbourne
load_unaligned_zeropad() reads a word that may run into an unmapped
page and recovers through an exception fixup. It does the word loads of
sized_strscpy() and of the name compare in dentry_string_cmp(), path
components in hash_name() and hashlen_string(), and the trailing bytes
in x86's csum_partial().
It is inline assembly on every architecture that implements it, so
KASAN and KCSAN never see these loads, while read_word_at_a_time() at
least checks the first byte of each word. A use-after-free or
out-of-bounds read of a name handed to path lookup goes unreported, and
so does strscpy() reading from a bad source, which the kasan_strings
KUnit case expects to be reported. On x86_64 with generic KASAN:
KASAN and string KUnit suites failures
before after
CONFIG_FORTIFY_SOURCE=n 1 0
CONFIG_FORTIFY_SOURCE=y 1 0
Check the first byte at the start of load_unaligned_zeropad() on every
architecture, as read_word_at_a_time() does. All callers only load a
word whose first byte belongs to the string or buffer they read, so
the check does not flag their intended reads past the end. Add no-op
stubs for the powerpc selftest that builds the header in user space.
Suggested-by: Peter Collingbourne <peter@pcc.me.uk>
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
arch/arm/include/asm/word-at-a-time.h | 7 +++++++
arch/arm64/include/asm/word-at-a-time.h | 7 +++++++
arch/powerpc/include/asm/word-at-a-time.h | 6 ++++++
arch/riscv/include/asm/word-at-a-time.h | 6 ++++++
arch/s390/include/asm/word-at-a-time.h | 6 ++++++
arch/x86/include/asm/word-at-a-time.h | 6 ++++++
tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h | 3 +++
tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h | 3 +++
8 files changed, 44 insertions(+)
diff --git a/arch/arm/include/asm/word-at-a-time.h b/arch/arm/include/asm/word-at-a-time.h
index 5023f98d8293d..f672aad2e30e6 100644
--- a/arch/arm/include/asm/word-at-a-time.h
+++ b/arch/arm/include/asm/word-at-a-time.h
@@ -56,6 +56,9 @@ static inline unsigned long find_zero(unsigned long mask)
#include <asm-generic/word-at-a-time.h>
#endif
+#include <linux/kasan-checks.h>
+#include <linux/kcsan-checks.h>
+
#ifdef CONFIG_DCACHE_WORD_ACCESS
/*
@@ -69,6 +72,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret, tmp;
+ /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
+ kasan_check_read(addr, 1);
+ kcsan_check_read(addr, 1);
+
/* Load word from unaligned pointer addr */
asm(
"1: ldr %0, [%2]\n"
diff --git a/arch/arm64/include/asm/word-at-a-time.h b/arch/arm64/include/asm/word-at-a-time.h
index 824ca6987a516..11ecca99057aa 100644
--- a/arch/arm64/include/asm/word-at-a-time.h
+++ b/arch/arm64/include/asm/word-at-a-time.h
@@ -40,6 +40,9 @@ static inline unsigned long zero_bytemask(unsigned long bits)
#include <asm-generic/word-at-a-time.h>
#endif
+#include <linux/kasan-checks.h>
+#include <linux/kcsan-checks.h>
+
/*
* Load an unaligned word from kernel space.
*
@@ -51,6 +54,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret;
+ /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
+ kasan_check_read(addr, 1);
+ kcsan_check_read(addr, 1);
+
__mte_enable_tco_async();
/* Load word from unaligned pointer addr */
diff --git a/arch/powerpc/include/asm/word-at-a-time.h b/arch/powerpc/include/asm/word-at-a-time.h
index 54653a8634149..49c6a1f65c493 100644
--- a/arch/powerpc/include/asm/word-at-a-time.h
+++ b/arch/powerpc/include/asm/word-at-a-time.h
@@ -5,6 +5,8 @@
* Word-at-a-time interfaces for PowerPC.
*/
#include <linux/bitops.h>
+#include <linux/kasan-checks.h>
+#include <linux/kcsan-checks.h>
#include <linux/wordpart.h>
#include <asm/asm-compat.h>
#include <asm/extable.h>
@@ -168,6 +170,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret, offset, tmp;
+ /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
+ kasan_check_read(addr, 1);
+ kcsan_check_read(addr, 1);
+
asm(
"1: " PPC_LL "%[ret], 0(%[addr])\n"
"2:\n"
diff --git a/arch/riscv/include/asm/word-at-a-time.h b/arch/riscv/include/asm/word-at-a-time.h
index 3802cda71ab7e..fb7e2904dd7fd 100644
--- a/arch/riscv/include/asm/word-at-a-time.h
+++ b/arch/riscv/include/asm/word-at-a-time.h
@@ -11,6 +11,8 @@
#include <asm/asm-extable.h>
#include <linux/bitops.h>
+#include <linux/kasan-checks.h>
+#include <linux/kcsan-checks.h>
#include <linux/wordpart.h>
struct word_at_a_time {
@@ -60,6 +62,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret;
+ /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
+ kasan_check_read(addr, 1);
+ kcsan_check_read(addr, 1);
+
/* Load word from unaligned pointer addr */
asm(
"1: " REG_L " %0, %2\n"
diff --git a/arch/s390/include/asm/word-at-a-time.h b/arch/s390/include/asm/word-at-a-time.h
index eaa19dee76994..59393cf065964 100644
--- a/arch/s390/include/asm/word-at-a-time.h
+++ b/arch/s390/include/asm/word-at-a-time.h
@@ -3,6 +3,8 @@
#define _ASM_WORD_AT_A_TIME_H
#include <linux/bitops.h>
+#include <linux/kasan-checks.h>
+#include <linux/kcsan-checks.h>
#include <linux/wordpart.h>
#include <asm/asm-extable.h>
#include <asm/bitsperlong.h>
@@ -52,6 +54,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long data;
+ /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
+ kasan_check_read(addr, 1);
+ kcsan_check_read(addr, 1);
+
asm_inline volatile(
"0: lg %[data],0(%[addr])\n"
"1: nopr %%r7\n"
diff --git a/arch/x86/include/asm/word-at-a-time.h b/arch/x86/include/asm/word-at-a-time.h
index 422a47746657a..1319e1d87a9c4 100644
--- a/arch/x86/include/asm/word-at-a-time.h
+++ b/arch/x86/include/asm/word-at-a-time.h
@@ -3,6 +3,8 @@
#define _ASM_WORD_AT_A_TIME_H
#include <linux/bitops.h>
+#include <linux/kasan-checks.h>
+#include <linux/kcsan-checks.h>
#include <linux/wordpart.h>
struct word_at_a_time {
@@ -71,6 +73,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret;
+ /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
+ kasan_check_read(addr, 1);
+ kcsan_check_read(addr, 1);
+
asm volatile(
"1: mov %[mem], %[ret]\n"
"2:\n"
diff --git a/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h b/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h
new file mode 100644
index 0000000000000..000c92de6baf2
--- /dev/null
+++ b/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* There is nothing for KASAN to check in user space. */
+#define kasan_check_read(p, size) ((void)(p), (void)(size))
diff --git a/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h b/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h
new file mode 100644
index 0000000000000..39c97bc15531e
--- /dev/null
+++ b/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* There is nothing for KCSAN to check in user space. */
+#define kcsan_check_read(p, size) ((void)(p), (void)(size))
--
2.43.0
^ permalink raw reply related [flat|nested] 14+ messages in thread* Re: [PATCH 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
2026-10-19 1:58 ` Qiliang Yuan
(?)
@ 2026-10-19 2:06 ` sashiko-bot
-1 siblings, 0 replies; 14+ messages in thread
From: sashiko-bot @ 2026-10-19 2:06 UTC (permalink / raw)
To: Qiliang Yuan
Cc: linux-s390, Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger
> load_unaligned_zeropad() reads a word that may run into an unmapped
> page and recovers through an exception fixup. It does the word loads of
> sized_strscpy() and of the name compare in dentry_string_cmp(), path
> components in hash_name() and hashlen_string(), and the trailing bytes
> in x86's csum_partial().
>
> It is inline assembly on every architecture that implements it, so
> KASAN and KCSAN never see these loads, while read_word_at_a_time() at
> least checks the first byte of each word. A use-after-free or
> out-of-bounds read of a name handed to path lookup goes unreported, and
> so does strscpy() reading from a bad source, which the kasan_strings
> KUnit case expects to be reported. On x86_64 with generic KASAN:
>
> KASAN and string KUnit suites failures
> before after
> CONFIG_FORTIFY_SOURCE=n 1 0
> CONFIG_FORTIFY_SOURCE=y 1 0
>
> Check the first byte at the start of load_unaligned_zeropad() on every
> architecture, as read_word_at_a_time() does. All callers only load a
> word whose first byte belongs to the string or buffer they read, so
> the check does not flag their intended reads past the end. Add no-op
> stubs for the powerpc selftest that builds the header in user space.
>
> Suggested-by: Peter Collingbourne <peter@pcc.me.uk>
> Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
Sashiko has reviewed this patch and found no issues.
--
Sashiko AI review · https://sashiko.dev/#/patchset/20261019-bug-word-at-a-time-zeropad-kasan-v1-0-f21a3ed6a477@gmail.com?part=1
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
2026-10-19 1:58 ` Qiliang Yuan
@ 2026-10-19 22:30 ` Marco Elver
-1 siblings, 0 replies; 14+ messages in thread
From: Marco Elver @ 2026-10-19 22:30 UTC (permalink / raw)
To: Qiliang Yuan
Cc: Russell King, Catalin Marinas, Will Deacon, Mark Rutland,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Ritesh Harjani (IBM),
Shrikanth Hegde, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Shuah Khan, Andrey Ryabinin, Alexander Potapenko,
Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Andrew Morton,
linux-arm-kernel, Kees Cook, linux-kernel, linux-hardening,
linuxppc-dev, linux-riscv, linux-s390, linux-kselftest, kasan-dev,
linux-mm, Peter Collingbourne
On Mon, 19 Oct 2026 at 03:58, Qiliang Yuan <odys.yuan@gmail.com> wrote:
>
> load_unaligned_zeropad() reads a word that may run into an unmapped
> page and recovers through an exception fixup. It does the word loads of
> sized_strscpy() and of the name compare in dentry_string_cmp(), path
> components in hash_name() and hashlen_string(), and the trailing bytes
> in x86's csum_partial().
>
> It is inline assembly on every architecture that implements it, so
> KASAN and KCSAN never see these loads, while read_word_at_a_time() at
> least checks the first byte of each word. A use-after-free or
> out-of-bounds read of a name handed to path lookup goes unreported, and
> so does strscpy() reading from a bad source, which the kasan_strings
> KUnit case expects to be reported. On x86_64 with generic KASAN:
>
> KASAN and string KUnit suites failures
> before after
> CONFIG_FORTIFY_SOURCE=n 1 0
> CONFIG_FORTIFY_SOURCE=y 1 0
>
> Check the first byte at the start of load_unaligned_zeropad() on every
> architecture, as read_word_at_a_time() does. All callers only load a
> word whose first byte belongs to the string or buffer they read, so
> the check does not flag their intended reads past the end. Add no-op
> stubs for the powerpc selftest that builds the header in user space.
>
> Suggested-by: Peter Collingbourne <peter@pcc.me.uk>
> Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
Reviewed-by: Marco Elver <elver@google.com>
Thanks!
> ---
> arch/arm/include/asm/word-at-a-time.h | 7 +++++++
> arch/arm64/include/asm/word-at-a-time.h | 7 +++++++
> arch/powerpc/include/asm/word-at-a-time.h | 6 ++++++
> arch/riscv/include/asm/word-at-a-time.h | 6 ++++++
> arch/s390/include/asm/word-at-a-time.h | 6 ++++++
> arch/x86/include/asm/word-at-a-time.h | 6 ++++++
> tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h | 3 +++
> tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h | 3 +++
> 8 files changed, 44 insertions(+)
>
> diff --git a/arch/arm/include/asm/word-at-a-time.h b/arch/arm/include/asm/word-at-a-time.h
> index 5023f98d8293d..f672aad2e30e6 100644
> --- a/arch/arm/include/asm/word-at-a-time.h
> +++ b/arch/arm/include/asm/word-at-a-time.h
> @@ -56,6 +56,9 @@ static inline unsigned long find_zero(unsigned long mask)
> #include <asm-generic/word-at-a-time.h>
> #endif
>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> +
> #ifdef CONFIG_DCACHE_WORD_ACCESS
>
> /*
> @@ -69,6 +72,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret, tmp;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> /* Load word from unaligned pointer addr */
> asm(
> "1: ldr %0, [%2]\n"
> diff --git a/arch/arm64/include/asm/word-at-a-time.h b/arch/arm64/include/asm/word-at-a-time.h
> index 824ca6987a516..11ecca99057aa 100644
> --- a/arch/arm64/include/asm/word-at-a-time.h
> +++ b/arch/arm64/include/asm/word-at-a-time.h
> @@ -40,6 +40,9 @@ static inline unsigned long zero_bytemask(unsigned long bits)
> #include <asm-generic/word-at-a-time.h>
> #endif
>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> +
> /*
> * Load an unaligned word from kernel space.
> *
> @@ -51,6 +54,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> __mte_enable_tco_async();
>
> /* Load word from unaligned pointer addr */
> diff --git a/arch/powerpc/include/asm/word-at-a-time.h b/arch/powerpc/include/asm/word-at-a-time.h
> index 54653a8634149..49c6a1f65c493 100644
> --- a/arch/powerpc/include/asm/word-at-a-time.h
> +++ b/arch/powerpc/include/asm/word-at-a-time.h
> @@ -5,6 +5,8 @@
> * Word-at-a-time interfaces for PowerPC.
> */
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
> #include <asm/asm-compat.h>
> #include <asm/extable.h>
> @@ -168,6 +170,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret, offset, tmp;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> asm(
> "1: " PPC_LL "%[ret], 0(%[addr])\n"
> "2:\n"
> diff --git a/arch/riscv/include/asm/word-at-a-time.h b/arch/riscv/include/asm/word-at-a-time.h
> index 3802cda71ab7e..fb7e2904dd7fd 100644
> --- a/arch/riscv/include/asm/word-at-a-time.h
> +++ b/arch/riscv/include/asm/word-at-a-time.h
> @@ -11,6 +11,8 @@
>
> #include <asm/asm-extable.h>
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
>
> struct word_at_a_time {
> @@ -60,6 +62,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> /* Load word from unaligned pointer addr */
> asm(
> "1: " REG_L " %0, %2\n"
> diff --git a/arch/s390/include/asm/word-at-a-time.h b/arch/s390/include/asm/word-at-a-time.h
> index eaa19dee76994..59393cf065964 100644
> --- a/arch/s390/include/asm/word-at-a-time.h
> +++ b/arch/s390/include/asm/word-at-a-time.h
> @@ -3,6 +3,8 @@
> #define _ASM_WORD_AT_A_TIME_H
>
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
> #include <asm/asm-extable.h>
> #include <asm/bitsperlong.h>
> @@ -52,6 +54,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long data;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> asm_inline volatile(
> "0: lg %[data],0(%[addr])\n"
> "1: nopr %%r7\n"
> diff --git a/arch/x86/include/asm/word-at-a-time.h b/arch/x86/include/asm/word-at-a-time.h
> index 422a47746657a..1319e1d87a9c4 100644
> --- a/arch/x86/include/asm/word-at-a-time.h
> +++ b/arch/x86/include/asm/word-at-a-time.h
> @@ -3,6 +3,8 @@
> #define _ASM_WORD_AT_A_TIME_H
>
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
>
> struct word_at_a_time {
> @@ -71,6 +73,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> asm volatile(
> "1: mov %[mem], %[ret]\n"
> "2:\n"
> diff --git a/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h b/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h
> new file mode 100644
> index 0000000000000..000c92de6baf2
> --- /dev/null
> +++ b/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h
> @@ -0,0 +1,3 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* There is nothing for KASAN to check in user space. */
> +#define kasan_check_read(p, size) ((void)(p), (void)(size))
> diff --git a/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h b/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h
> new file mode 100644
> index 0000000000000..39c97bc15531e
> --- /dev/null
> +++ b/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h
> @@ -0,0 +1,3 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* There is nothing for KCSAN to check in user space. */
> +#define kcsan_check_read(p, size) ((void)(p), (void)(size))
>
> --
> 2.43.0
>
> --
> You received this message because you are subscribed to the Google Groups "kasan-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kasan-dev+unsubscribe@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/kasan-dev/20261019-bug-word-at-a-time-zeropad-kasan-v1-1-f21a3ed6a477%40gmail.com.
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
@ 2026-10-19 22:30 ` Marco Elver
0 siblings, 0 replies; 14+ messages in thread
From: Marco Elver @ 2026-10-19 22:30 UTC (permalink / raw)
To: Qiliang Yuan
Cc: Russell King, Catalin Marinas, Will Deacon, Mark Rutland,
Madhavan Srinivasan, Michael Ellerman, Nicholas Piggin,
Christophe Leroy (CS GROUP), Ritesh Harjani (IBM),
Shrikanth Hegde, Paul Walmsley, Palmer Dabbelt, Albert Ou,
Alexandre Ghiti, Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger, Sven Schnelle, Thomas Gleixner,
Ingo Molnar, Borislav Petkov, Dave Hansen, x86, H. Peter Anvin,
Shuah Khan, Andrey Ryabinin, Alexander Potapenko,
Andrey Konovalov, Dmitry Vyukov, Vincenzo Frascino, Andrew Morton,
linux-arm-kernel, Kees Cook, linux-kernel, linux-hardening,
linuxppc-dev, linux-riscv, linux-s390, linux-kselftest, kasan-dev,
linux-mm, Peter Collingbourne
On Mon, 19 Oct 2026 at 03:58, Qiliang Yuan <odys.yuan@gmail.com> wrote:
>
> load_unaligned_zeropad() reads a word that may run into an unmapped
> page and recovers through an exception fixup. It does the word loads of
> sized_strscpy() and of the name compare in dentry_string_cmp(), path
> components in hash_name() and hashlen_string(), and the trailing bytes
> in x86's csum_partial().
>
> It is inline assembly on every architecture that implements it, so
> KASAN and KCSAN never see these loads, while read_word_at_a_time() at
> least checks the first byte of each word. A use-after-free or
> out-of-bounds read of a name handed to path lookup goes unreported, and
> so does strscpy() reading from a bad source, which the kasan_strings
> KUnit case expects to be reported. On x86_64 with generic KASAN:
>
> KASAN and string KUnit suites failures
> before after
> CONFIG_FORTIFY_SOURCE=n 1 0
> CONFIG_FORTIFY_SOURCE=y 1 0
>
> Check the first byte at the start of load_unaligned_zeropad() on every
> architecture, as read_word_at_a_time() does. All callers only load a
> word whose first byte belongs to the string or buffer they read, so
> the check does not flag their intended reads past the end. Add no-op
> stubs for the powerpc selftest that builds the header in user space.
>
> Suggested-by: Peter Collingbourne <peter@pcc.me.uk>
> Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
Reviewed-by: Marco Elver <elver@google.com>
Thanks!
> ---
> arch/arm/include/asm/word-at-a-time.h | 7 +++++++
> arch/arm64/include/asm/word-at-a-time.h | 7 +++++++
> arch/powerpc/include/asm/word-at-a-time.h | 6 ++++++
> arch/riscv/include/asm/word-at-a-time.h | 6 ++++++
> arch/s390/include/asm/word-at-a-time.h | 6 ++++++
> arch/x86/include/asm/word-at-a-time.h | 6 ++++++
> tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h | 3 +++
> tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h | 3 +++
> 8 files changed, 44 insertions(+)
>
> diff --git a/arch/arm/include/asm/word-at-a-time.h b/arch/arm/include/asm/word-at-a-time.h
> index 5023f98d8293d..f672aad2e30e6 100644
> --- a/arch/arm/include/asm/word-at-a-time.h
> +++ b/arch/arm/include/asm/word-at-a-time.h
> @@ -56,6 +56,9 @@ static inline unsigned long find_zero(unsigned long mask)
> #include <asm-generic/word-at-a-time.h>
> #endif
>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> +
> #ifdef CONFIG_DCACHE_WORD_ACCESS
>
> /*
> @@ -69,6 +72,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret, tmp;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> /* Load word from unaligned pointer addr */
> asm(
> "1: ldr %0, [%2]\n"
> diff --git a/arch/arm64/include/asm/word-at-a-time.h b/arch/arm64/include/asm/word-at-a-time.h
> index 824ca6987a516..11ecca99057aa 100644
> --- a/arch/arm64/include/asm/word-at-a-time.h
> +++ b/arch/arm64/include/asm/word-at-a-time.h
> @@ -40,6 +40,9 @@ static inline unsigned long zero_bytemask(unsigned long bits)
> #include <asm-generic/word-at-a-time.h>
> #endif
>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> +
> /*
> * Load an unaligned word from kernel space.
> *
> @@ -51,6 +54,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> __mte_enable_tco_async();
>
> /* Load word from unaligned pointer addr */
> diff --git a/arch/powerpc/include/asm/word-at-a-time.h b/arch/powerpc/include/asm/word-at-a-time.h
> index 54653a8634149..49c6a1f65c493 100644
> --- a/arch/powerpc/include/asm/word-at-a-time.h
> +++ b/arch/powerpc/include/asm/word-at-a-time.h
> @@ -5,6 +5,8 @@
> * Word-at-a-time interfaces for PowerPC.
> */
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
> #include <asm/asm-compat.h>
> #include <asm/extable.h>
> @@ -168,6 +170,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret, offset, tmp;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> asm(
> "1: " PPC_LL "%[ret], 0(%[addr])\n"
> "2:\n"
> diff --git a/arch/riscv/include/asm/word-at-a-time.h b/arch/riscv/include/asm/word-at-a-time.h
> index 3802cda71ab7e..fb7e2904dd7fd 100644
> --- a/arch/riscv/include/asm/word-at-a-time.h
> +++ b/arch/riscv/include/asm/word-at-a-time.h
> @@ -11,6 +11,8 @@
>
> #include <asm/asm-extable.h>
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
>
> struct word_at_a_time {
> @@ -60,6 +62,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> /* Load word from unaligned pointer addr */
> asm(
> "1: " REG_L " %0, %2\n"
> diff --git a/arch/s390/include/asm/word-at-a-time.h b/arch/s390/include/asm/word-at-a-time.h
> index eaa19dee76994..59393cf065964 100644
> --- a/arch/s390/include/asm/word-at-a-time.h
> +++ b/arch/s390/include/asm/word-at-a-time.h
> @@ -3,6 +3,8 @@
> #define _ASM_WORD_AT_A_TIME_H
>
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
> #include <asm/asm-extable.h>
> #include <asm/bitsperlong.h>
> @@ -52,6 +54,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long data;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> asm_inline volatile(
> "0: lg %[data],0(%[addr])\n"
> "1: nopr %%r7\n"
> diff --git a/arch/x86/include/asm/word-at-a-time.h b/arch/x86/include/asm/word-at-a-time.h
> index 422a47746657a..1319e1d87a9c4 100644
> --- a/arch/x86/include/asm/word-at-a-time.h
> +++ b/arch/x86/include/asm/word-at-a-time.h
> @@ -3,6 +3,8 @@
> #define _ASM_WORD_AT_A_TIME_H
>
> #include <linux/bitops.h>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> #include <linux/wordpart.h>
>
> struct word_at_a_time {
> @@ -71,6 +73,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> asm volatile(
> "1: mov %[mem], %[ret]\n"
> "2:\n"
> diff --git a/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h b/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h
> new file mode 100644
> index 0000000000000..000c92de6baf2
> --- /dev/null
> +++ b/tools/testing/selftests/powerpc/primitives/linux/kasan-checks.h
> @@ -0,0 +1,3 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* There is nothing for KASAN to check in user space. */
> +#define kasan_check_read(p, size) ((void)(p), (void)(size))
> diff --git a/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h b/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h
> new file mode 100644
> index 0000000000000..39c97bc15531e
> --- /dev/null
> +++ b/tools/testing/selftests/powerpc/primitives/linux/kcsan-checks.h
> @@ -0,0 +1,3 @@
> +/* SPDX-License-Identifier: GPL-2.0 */
> +/* There is nothing for KCSAN to check in user space. */
> +#define kcsan_check_read(p, size) ((void)(p), (void)(size))
>
> --
> 2.43.0
>
> --
> You received this message because you are subscribed to the Google Groups "kasan-dev" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to kasan-dev+unsubscribe@googlegroups.com.
> To view this discussion visit https://groups.google.com/d/msgid/kasan-dev/20261019-bug-word-at-a-time-zeropad-kasan-v1-1-f21a3ed6a477%40gmail.com.
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 14+ messages in thread
* Re: [PATCH 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
2026-10-19 1:58 ` Qiliang Yuan
@ 2026-10-20 16:58 ` Andrey Ryabinin
-1 siblings, 0 replies; 14+ messages in thread
From: Andrey Ryabinin @ 2026-10-20 16:58 UTC (permalink / raw)
To: Qiliang Yuan, Russell King, Catalin Marinas, Will Deacon,
Mark Rutland, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP),
Ritesh Harjani (IBM), Shrikanth Hegde, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Heiko Carstens,
Vasily Gorbik, Alexander Gordeev, Christian Borntraeger,
Sven Schnelle, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Shuah Khan, Marco Elver,
Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov,
Vincenzo Frascino, Andrew Morton
Cc: linux-arm-kernel, Kees Cook, linux-kernel, linux-hardening,
linuxppc-dev, linux-riscv, linux-s390, linux-kselftest, kasan-dev,
linux-mm, Peter Collingbourne
Qiliang Yuan <odys.yuan@gmail.com> writes:
>
> diff --git a/arch/arm/include/asm/word-at-a-time.h b/arch/arm/include/asm/word-at-a-time.h
> index 5023f98d8293d..f672aad2e30e6 100644
> --- a/arch/arm/include/asm/word-at-a-time.h
> +++ b/arch/arm/include/asm/word-at-a-time.h
> @@ -56,6 +56,9 @@ static inline unsigned long find_zero(unsigned long mask)
> #include <asm-generic/word-at-a-time.h>
> #endif
>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> +
> #ifdef CONFIG_DCACHE_WORD_ACCESS
>
> /*
> @@ -69,6 +72,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret, tmp;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
Why it's open-coded ?
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> /* Load word from unaligned pointer addr */
> asm(
> "1: ldr %0, [%2]\n"
^ permalink raw reply [flat|nested] 14+ messages in thread* Re: [PATCH 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
@ 2026-10-20 16:58 ` Andrey Ryabinin
0 siblings, 0 replies; 14+ messages in thread
From: Andrey Ryabinin @ 2026-10-20 16:58 UTC (permalink / raw)
To: Qiliang Yuan, Russell King, Catalin Marinas, Will Deacon,
Mark Rutland, Madhavan Srinivasan, Michael Ellerman,
Nicholas Piggin, Christophe Leroy (CS GROUP),
Ritesh Harjani (IBM), Shrikanth Hegde, Paul Walmsley,
Palmer Dabbelt, Albert Ou, Alexandre Ghiti, Heiko Carstens,
Vasily Gorbik, Alexander Gordeev, Christian Borntraeger,
Sven Schnelle, Thomas Gleixner, Ingo Molnar, Borislav Petkov,
Dave Hansen, x86, H. Peter Anvin, Shuah Khan, Marco Elver,
Alexander Potapenko, Andrey Konovalov, Dmitry Vyukov,
Vincenzo Frascino, Andrew Morton
Cc: linux-arm-kernel, Kees Cook, linux-kernel, linux-hardening,
linuxppc-dev, linux-riscv, linux-s390, linux-kselftest, kasan-dev,
linux-mm, Peter Collingbourne
Qiliang Yuan <odys.yuan@gmail.com> writes:
>
> diff --git a/arch/arm/include/asm/word-at-a-time.h b/arch/arm/include/asm/word-at-a-time.h
> index 5023f98d8293d..f672aad2e30e6 100644
> --- a/arch/arm/include/asm/word-at-a-time.h
> +++ b/arch/arm/include/asm/word-at-a-time.h
> @@ -56,6 +56,9 @@ static inline unsigned long find_zero(unsigned long mask)
> #include <asm-generic/word-at-a-time.h>
> #endif
>
> +#include <linux/kasan-checks.h>
> +#include <linux/kcsan-checks.h>
> +
> #ifdef CONFIG_DCACHE_WORD_ACCESS
>
> /*
> @@ -69,6 +72,10 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
> {
> unsigned long ret, tmp;
>
> + /* open-coded instrument_read(addr, 1), as in read_word_at_a_time() */
Why it's open-coded ?
> + kasan_check_read(addr, 1);
> + kcsan_check_read(addr, 1);
> +
> /* Load word from unaligned pointer addr */
> asm(
> "1: ldr %0, [%2]\n"
_______________________________________________
linux-riscv mailing list
linux-riscv@lists.infradead.org
http://lists.infradead.org/mailman/listinfo/linux-riscv
^ permalink raw reply [flat|nested] 14+ messages in thread