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 Signed-off-by: Qiliang Yuan Reviewed-by: Marco Elver --- 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 #endif +#include + #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 #include +#include #include 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 +#include #include #include #include @@ -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 #include +#include #include 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 +#include #include #include #include @@ -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 +#include #include 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