On Mon, 19 Oct 2026 at 03:58, Qiliang Yuan <[email protected]> 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 <[email protected]> > Signed-off-by: Qiliang Yuan <[email protected]>
Reviewed-by: Marco Elver <[email protected]> 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 [email protected]. > 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.
