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]>
---
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