* [PATCH v2 0/2] word-at-a-time: let KASAN see load_unaligned_zeropad()
@ 2026-10-23 1:49 Qiliang Yuan
2026-10-23 1:49 ` [PATCH v2 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad() Qiliang Yuan
2026-10-23 1:49 ` [PATCH v2 2/2] kasan/test: cover load_unaligned_zeropad() Qiliang Yuan
0 siblings, 2 replies; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-23 1:49 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() is inline assembly, so KASAN and KCSAN never
see the word loads that sized_strscpy(), the dcache name compare, path
name hashing and x86 csum_partial() do through it. While reviewing a
strscpy() KASAN fix, Peter Collingbourne suggested checking the first
byte inside load_unaligned_zeropad() itself, as read_word_at_a_time()
does [1].
Patch 1 adds that check on the six architectures that implement
load_unaligned_zeropad(). Patch 2 adds a KASAN KUnit case for it.
[1] https://lore.kernel.org/r/CAPQLkRhXZwozmfhNd_PEx1ihwdH1_5qthP6PANYJ_m-v9vfQTw@mail.gmail.com
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
---
V1 -> V2:
- Call instrument_read() instead of open-coding it (Andrey Ryabinin)
- Keep Marco Elver's Reviewed-by, as the check itself is unchanged
- Rebase onto 7be4bc52fd9e, which drops the big-endian branch of the
arm64 header and rewrites the s390 load_unaligned_zeropad()
v1: https://lore.kernel.org/r/20261019-bug-word-at-a-time-zeropad-kasan-v1-0-f21a3ed6a477@gmail.com
---
Qiliang Yuan (2):
word-at-a-time: check the first byte in load_unaligned_zeropad()
kasan/test: cover load_unaligned_zeropad()
arch/arm/include/asm/word-at-a-time.h | 5 +++++
arch/arm64/include/asm/word-at-a-time.h | 4 ++++
arch/powerpc/include/asm/word-at-a-time.h | 4 ++++
arch/riscv/include/asm/word-at-a-time.h | 4 ++++
arch/s390/include/asm/word-at-a-time.h | 4 ++++
arch/x86/include/asm/word-at-a-time.h | 4 ++++
mm/kasan/kasan_test_c.c | 24 ++++++++++++++++++++++
.../powerpc/primitives/linux/instrumented.h | 3 +++
8 files changed, 52 insertions(+)
---
base-commit: 7be4bc52fd9e0e55dc9a2e0232b89427d2c4b614
change-id: 20261019-bug-word-at-a-time-zeropad-kasan-af2460466ee6
Best regards,
--
Qiliang Yuan <odys.yuan@gmail.com>
^ permalink raw reply [flat|nested] 5+ messages in thread
* [PATCH v2 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
2026-10-23 1:49 [PATCH v2 0/2] word-at-a-time: let KASAN see load_unaligned_zeropad() Qiliang Yuan
@ 2026-10-23 1:49 ` Qiliang Yuan
2026-10-23 1:57 ` sashiko-bot
2026-10-23 1:49 ` [PATCH v2 2/2] kasan/test: cover load_unaligned_zeropad() Qiliang Yuan
1 sibling, 1 reply; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-23 1:49 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 with instrument_read() 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 a no-op instrument_read() 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>
---
arch/arm/include/asm/word-at-a-time.h | 5 +++++
arch/arm64/include/asm/word-at-a-time.h | 4 ++++
arch/powerpc/include/asm/word-at-a-time.h | 4 ++++
arch/riscv/include/asm/word-at-a-time.h | 4 ++++
arch/s390/include/asm/word-at-a-time.h | 4 ++++
arch/x86/include/asm/word-at-a-time.h | 4 ++++
tools/testing/selftests/powerpc/primitives/linux/instrumented.h | 3 +++
7 files changed, 28 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..361771f178699 100644
--- a/arch/arm/include/asm/word-at-a-time.h
+++ b/arch/arm/include/asm/word-at-a-time.h
@@ -56,6 +56,8 @@ static inline unsigned long find_zero(unsigned long mask)
#include <asm-generic/word-at-a-time.h>
#endif
+#include <linux/instrumented.h>
+
#ifdef CONFIG_DCACHE_WORD_ACCESS
/*
@@ -69,6 +71,9 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret, tmp;
+ /* Check only the first byte, the rest may lie past the object */
+ instrument_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 d7a3b69dc8c6f..37d45e716c11a 100644
--- a/arch/arm64/include/asm/word-at-a-time.h
+++ b/arch/arm64/include/asm/word-at-a-time.h
@@ -8,6 +8,7 @@
#include <linux/uaccess.h>
#include <linux/bitops.h>
+#include <linux/instrumented.h>
#include <linux/wordpart.h>
struct word_at_a_time {
@@ -45,6 +46,9 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret;
+ /* Check only the first byte, the rest may lie past the object */
+ instrument_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..1660b2e3e3a8f 100644
--- a/arch/powerpc/include/asm/word-at-a-time.h
+++ b/arch/powerpc/include/asm/word-at-a-time.h
@@ -5,6 +5,7 @@
* Word-at-a-time interfaces for PowerPC.
*/
#include <linux/bitops.h>
+#include <linux/instrumented.h>
#include <linux/wordpart.h>
#include <asm/asm-compat.h>
#include <asm/extable.h>
@@ -168,6 +169,9 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret, offset, tmp;
+ /* Check only the first byte, the rest may lie past the object */
+ instrument_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..761fa2734b7f5 100644
--- a/arch/riscv/include/asm/word-at-a-time.h
+++ b/arch/riscv/include/asm/word-at-a-time.h
@@ -11,6 +11,7 @@
#include <asm/asm-extable.h>
#include <linux/bitops.h>
+#include <linux/instrumented.h>
#include <linux/wordpart.h>
struct word_at_a_time {
@@ -60,6 +61,9 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret;
+ /* Check only the first byte, the rest may lie past the object */
+ instrument_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 0b7779c8d5a4e..f3caa4959cc87 100644
--- a/arch/s390/include/asm/word-at-a-time.h
+++ b/arch/s390/include/asm/word-at-a-time.h
@@ -3,6 +3,7 @@
#define _ASM_WORD_AT_A_TIME_H
#include <linux/bitops.h>
+#include <linux/instrumented.h>
#include <linux/wordpart.h>
#include <asm/asm-extable.h>
#include <asm/bitsperlong.h>
@@ -52,6 +53,9 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long data;
+ /* Check only the first byte, the rest may lie past the object */
+ instrument_read(addr, 1);
+
asm_inline volatile(
"0: lg %[data],0(%[addr])\n"
"1:\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..5374e08372253 100644
--- a/arch/x86/include/asm/word-at-a-time.h
+++ b/arch/x86/include/asm/word-at-a-time.h
@@ -3,6 +3,7 @@
#define _ASM_WORD_AT_A_TIME_H
#include <linux/bitops.h>
+#include <linux/instrumented.h>
#include <linux/wordpart.h>
struct word_at_a_time {
@@ -71,6 +72,9 @@ static inline unsigned long load_unaligned_zeropad(const void *addr)
{
unsigned long ret;
+ /* Check only the first byte, the rest may lie past the object */
+ instrument_read(addr, 1);
+
asm volatile(
"1: mov %[mem], %[ret]\n"
"2:\n"
diff --git a/tools/testing/selftests/powerpc/primitives/linux/instrumented.h b/tools/testing/selftests/powerpc/primitives/linux/instrumented.h
new file mode 100644
index 0000000000000..114ef9284f69b
--- /dev/null
+++ b/tools/testing/selftests/powerpc/primitives/linux/instrumented.h
@@ -0,0 +1,3 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/* There is nothing for KASAN or KCSAN to check in user space. */
+#define instrument_read(p, size) ((void)(p), (void)(size))
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* [PATCH v2 2/2] kasan/test: cover load_unaligned_zeropad()
2026-10-23 1:49 [PATCH v2 0/2] word-at-a-time: let KASAN see load_unaligned_zeropad() Qiliang Yuan
2026-10-23 1:49 ` [PATCH v2 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad() Qiliang Yuan
@ 2026-10-23 1:49 ` Qiliang Yuan
2026-10-23 1:57 ` sashiko-bot
1 sibling, 1 reply; 5+ messages in thread
From: Qiliang Yuan @ 2026-10-23 1:49 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
On architectures with CONFIG_DCACHE_WORD_ACCESS, load_unaligned_zeropad()
does the word loads of the dcache name compare and of path name
hashing.
None of the KASAN test cases reads memory through it, so nothing
noticed that KASAN did not check these loads at all.
Add a case that reads a freed object through load_unaligned_zeropad()
and expects a report. Skip it with hardware tag-based KASAN, since
arm64 turns tag checking off for this load:
kasan_load_unaligned_zeropad, x86_64, generic KASAN
without "word-at-a-time: check the first byte in
load_unaligned_zeropad()" FAILED
with it PASSED
Signed-off-by: Qiliang Yuan <odys.yuan@gmail.com>
Reviewed-by: Andrey Ryabinin <ryabinin.a.a@gmail.com>
---
mm/kasan/kasan_test_c.c | 24 ++++++++++++++++++++++++
1 file changed, 24 insertions(+)
diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c
index b9e167ed5be32..c791512f6fc49 100644
--- a/mm/kasan/kasan_test_c.c
+++ b/mm/kasan/kasan_test_c.c
@@ -28,6 +28,9 @@
#include <trace/events/printk.h>
#include <asm/page.h>
+#ifdef CONFIG_DCACHE_WORD_ACCESS
+#include <asm/word-at-a-time.h>
+#endif
#include "kasan.h"
@@ -1673,6 +1676,26 @@ static void kasan_memcmp(struct kunit *test)
kfree(ptr);
}
+static void kasan_load_unaligned_zeropad(struct kunit *test)
+{
+#ifdef CONFIG_DCACHE_WORD_ACCESS
+ char *ptr;
+
+ /* The tag check is turned off for this load on arm64 with MTE. */
+ KASAN_TEST_NEEDS_CONFIG_OFF(test, CONFIG_KASAN_HW_TAGS);
+
+ ptr = kmalloc(64, GFP_KERNEL);
+ KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr);
+ kfree(ptr);
+
+ OPTIMIZER_HIDE_VAR(ptr);
+ KUNIT_EXPECT_KASAN_FAIL_READ(test,
+ kasan_int_result = load_unaligned_zeropad(ptr));
+#else
+ kunit_skip(test, "Test requires CONFIG_DCACHE_WORD_ACCESS=y");
+#endif
+}
+
static void kasan_strings(struct kunit *test)
{
char *ptr;
@@ -2264,6 +2287,7 @@ static struct kunit_case kasan_kunit_test_cases[] = {
KUNIT_CASE(kasan_alloca_oob_right),
KUNIT_CASE(kasan_memchr),
KUNIT_CASE(kasan_memcmp),
+ KUNIT_CASE(kasan_load_unaligned_zeropad),
KUNIT_CASE(kasan_strings),
KUNIT_CASE(kasan_bitops_generic),
KUNIT_CASE(kasan_bitops_tags),
--
2.43.0
^ permalink raw reply related [flat|nested] 5+ messages in thread
* Re: [PATCH v2 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad()
2026-10-23 1:49 ` [PATCH v2 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad() Qiliang Yuan
@ 2026-10-23 1:57 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-10-23 1:57 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 with instrument_read() 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 a no-op instrument_read() 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/20261023-bug-word-at-a-time-zeropad-kasan-v2-0-be0ecfbae847@gmail.com?part=1
^ permalink raw reply [flat|nested] 5+ messages in thread
* Re: [PATCH v2 2/2] kasan/test: cover load_unaligned_zeropad()
2026-10-23 1:49 ` [PATCH v2 2/2] kasan/test: cover load_unaligned_zeropad() Qiliang Yuan
@ 2026-10-23 1:57 ` sashiko-bot
0 siblings, 0 replies; 5+ messages in thread
From: sashiko-bot @ 2026-10-23 1:57 UTC (permalink / raw)
To: Qiliang Yuan
Cc: linux-s390, Heiko Carstens, Vasily Gorbik, Alexander Gordeev,
Christian Borntraeger
> On architectures with CONFIG_DCACHE_WORD_ACCESS, load_unaligned_zeropad()
> does the word loads of the dcache name compare and of path name
> hashing.
>
> None of the KASAN test cases reads memory through it, so nothing
> noticed that KASAN did not check these loads at all.
>
> Add a case that reads a freed object through load_unaligned_zeropad()
> and expects a report. Skip it with hardware tag-based KASAN, since
> arm64 turns tag checking off for this load:
>
> kasan_load_unaligned_zeropad, x86_64, generic KASAN
> without "word-at-a-time: check the first byte in
> load_unaligned_zeropad()" FAILED
> with it PASSED
>
> 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/20261023-bug-word-at-a-time-zeropad-kasan-v2-0-be0ecfbae847@gmail.com?part=2
^ permalink raw reply [flat|nested] 5+ messages in thread
end of thread, other threads:[~2026-10-23 1:57 UTC | newest]
Thread overview: 5+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-10-23 1:49 [PATCH v2 0/2] word-at-a-time: let KASAN see load_unaligned_zeropad() Qiliang Yuan
2026-10-23 1:49 ` [PATCH v2 1/2] word-at-a-time: check the first byte in load_unaligned_zeropad() Qiliang Yuan
2026-10-23 1:57 ` sashiko-bot
2026-10-23 1:49 ` [PATCH v2 2/2] kasan/test: cover load_unaligned_zeropad() Qiliang Yuan
2026-10-23 1:57 ` sashiko-bot
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox