From nobody Fri Oct 3 07:42:45 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id 6BB6C1EDA2A; Wed, 3 Sep 2025 15:00:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756911647; cv=none; b=NrGMkfnKfeaAFZ8Jlqj1b0yVh7iaxlJV8NMRlIh/hde4q2wa7XV1FjF5tJwD4++7IMYCgk8W/ZOhGJJbmNys9WQ7QuUdHgboTvafdmXGLDl3iM5oTr3nj/9p9Ul/Kscm/WUdF7Pzb5B1r2O0FFijxat96GQkUENl5ONrmf50+NQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756911647; c=relaxed/simple; bh=B3C1SOaLJFX7BHqi6AIKvQrJOUKzy4elw708iL9PLNg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=ORX9DIIdgdvaGIgTCYMoafh/xJDxDhkIToxBWajYk9tW9EoCl07j1N6P5oYWJkr6h0z9cwFUhdYFg092LdkU5ZjJ+tg4pVMEtTk+6auVrQZNlvO9QnTLDB1LVne4Ew1myHwK9kRXDG+MRwTA6V99EPYR9brHf24MxH8nKSRvSJM= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id 68A57168F; Wed, 3 Sep 2025 08:00:36 -0700 (PDT) Received: from e129823.cambridge.arm.com (e129823.arm.com [10.1.197.6]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id DA68F3F694; Wed, 3 Sep 2025 08:00:40 -0700 (PDT) From: Yeoreum Yun To: ryabinin.a.a@gmail.com, glider@google.com, andreyknvl@gmail.com, dvyukov@google.com, vincenzo.frascino@arm.com, corbet@lwn.net, catalin.marinas@arm.com, will@kernel.org, akpm@linux-foundation.org, scott@os.amperecomputing.com, jhubbard@nvidia.com, pankaj.gupta@amd.com, leitao@debian.org, kaleshsingh@google.com, maz@kernel.org, broonie@kernel.org, oliver.upton@linux.dev, james.morse@arm.com, ardb@kernel.org, hardevsinh.palaniya@siliconsignals.io, david@redhat.com, yang@os.amperecomputing.com Cc: kasan-dev@googlegroups.com, workflows@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-mm@kvack.org, Yeoreum Yun Subject: [PATCH v7 1/2] kasan/hw-tags: introduce kasan.write_only option Date: Wed, 3 Sep 2025 16:00:19 +0100 Message-Id: <20250903150020.1131840-2-yeoreum.yun@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250903150020.1131840-1-yeoreum.yun@arm.com> References: <20250903150020.1131840-1-yeoreum.yun@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" Since Armv8.9, FEATURE_MTE_STORE_ONLY feature is introduced to restrict raise of tag check fault on store operation only. Introcude KASAN write only mode based on this feature. KASAN write only mode restricts KASAN checks operation for write only and omits the checks for fetch/read operations when accessing memory. So it might be used not only debugging enviroment but also normal enviroment to check memory safty. This features can be controlled with "kasan.write_only" arguments. When "kasan.write_only=3Don", KASAN checks write operation only otherwise KASAN checks all operations. This changes the MTE_STORE_ONLY feature as BOOT_CPU_FEATURE like ARM64_MTE_ASYMM so that makes it initialise in kasan_init_hw_tags() with other function together. Signed-off-by: Yeoreum Yun Reviewed-by: Andrey Konovalov Reviewed-by: Catalin Marinas --- Documentation/dev-tools/kasan.rst | 3 ++ arch/arm64/include/asm/memory.h | 1 + arch/arm64/include/asm/mte-kasan.h | 6 +++ arch/arm64/kernel/cpufeature.c | 2 +- arch/arm64/kernel/mte.c | 18 ++++++++ mm/kasan/hw_tags.c | 70 +++++++++++++++++++++++++++++- mm/kasan/kasan.h | 7 +++ 7 files changed, 104 insertions(+), 3 deletions(-) diff --git a/Documentation/dev-tools/kasan.rst b/Documentation/dev-tools/ka= san.rst index 0a1418ab72fd..a034700da7c4 100644 --- a/Documentation/dev-tools/kasan.rst +++ b/Documentation/dev-tools/kasan.rst @@ -143,6 +143,9 @@ disabling KASAN altogether or controlling its features: Asymmetric mode: a bad access is detected synchronously on reads and asynchronously on writes. =20 +- ``kasan.write_only=3Doff`` or ``kasan.write_only=3Don`` controls whether= KASAN + checks the write (store) accesses only or all accesses (default: ``off``= ). + - ``kasan.vmalloc=3Doff`` or ``=3Don`` disables or enables tagging of vmal= loc allocations (default: ``on``). =20 diff --git a/arch/arm64/include/asm/memory.h b/arch/arm64/include/asm/memor= y.h index 5213248e081b..f1505c4acb38 100644 --- a/arch/arm64/include/asm/memory.h +++ b/arch/arm64/include/asm/memory.h @@ -308,6 +308,7 @@ static inline const void *__tag_set(const void *addr, u= 8 tag) #define arch_enable_tag_checks_sync() mte_enable_kernel_sync() #define arch_enable_tag_checks_async() mte_enable_kernel_async() #define arch_enable_tag_checks_asymm() mte_enable_kernel_asymm() +#define arch_enable_tag_checks_write_only() mte_enable_kernel_store_only() #define arch_suppress_tag_checks_start() mte_enable_tco() #define arch_suppress_tag_checks_stop() mte_disable_tco() #define arch_force_async_tag_fault() mte_check_tfsr_exit() diff --git a/arch/arm64/include/asm/mte-kasan.h b/arch/arm64/include/asm/mt= e-kasan.h index 2e98028c1965..0f9b08e8fb8d 100644 --- a/arch/arm64/include/asm/mte-kasan.h +++ b/arch/arm64/include/asm/mte-kasan.h @@ -200,6 +200,7 @@ static inline void mte_set_mem_tag_range(void *addr, si= ze_t size, u8 tag, void mte_enable_kernel_sync(void); void mte_enable_kernel_async(void); void mte_enable_kernel_asymm(void); +int mte_enable_kernel_store_only(void); =20 #else /* CONFIG_ARM64_MTE */ =20 @@ -251,6 +252,11 @@ static inline void mte_enable_kernel_asymm(void) { } =20 +static inline int mte_enable_kernel_store_only(void) +{ + return -EINVAL; +} + #endif /* CONFIG_ARM64_MTE */ =20 #endif /* __ASSEMBLY__ */ diff --git a/arch/arm64/kernel/cpufeature.c b/arch/arm64/kernel/cpufeature.c index ef269a5a37e1..1f6e8c87aae7 100644 --- a/arch/arm64/kernel/cpufeature.c +++ b/arch/arm64/kernel/cpufeature.c @@ -2945,7 +2945,7 @@ static const struct arm64_cpu_capabilities arm64_feat= ures[] =3D { { .desc =3D "Store Only MTE Tag Check", .capability =3D ARM64_MTE_STORE_ONLY, - .type =3D ARM64_CPUCAP_SYSTEM_FEATURE, + .type =3D ARM64_CPUCAP_BOOT_CPU_FEATURE, .matches =3D has_cpuid_feature, ARM64_CPUID_FIELDS(ID_AA64PFR2_EL1, MTESTOREONLY, IMP) }, diff --git a/arch/arm64/kernel/mte.c b/arch/arm64/kernel/mte.c index e5e773844889..54a52dc5c1ae 100644 --- a/arch/arm64/kernel/mte.c +++ b/arch/arm64/kernel/mte.c @@ -157,6 +157,24 @@ void mte_enable_kernel_asymm(void) mte_enable_kernel_sync(); } } + +int mte_enable_kernel_store_only(void) +{ + /* + * If the CPU does not support MTE store only, + * the kernel checks all operations. + */ + if (!cpus_have_cap(ARM64_MTE_STORE_ONLY)) + return -EINVAL; + + sysreg_clear_set(sctlr_el1, SCTLR_EL1_TCSO_MASK, + SYS_FIELD_PREP(SCTLR_EL1, TCSO, 1)); + isb(); + + pr_info_once("MTE: enabled store only mode at EL1\n"); + + return 0; +} #endif =20 #ifdef CONFIG_KASAN_HW_TAGS diff --git a/mm/kasan/hw_tags.c b/mm/kasan/hw_tags.c index 9a6927394b54..d5b5fb47d52b 100644 --- a/mm/kasan/hw_tags.c +++ b/mm/kasan/hw_tags.c @@ -41,9 +41,16 @@ enum kasan_arg_vmalloc { KASAN_ARG_VMALLOC_ON, }; =20 +enum kasan_arg_write_only { + KASAN_ARG_WRITE_ONLY_DEFAULT, + KASAN_ARG_WRITE_ONLY_OFF, + KASAN_ARG_WRITE_ONLY_ON, +}; + static enum kasan_arg kasan_arg __ro_after_init; static enum kasan_arg_mode kasan_arg_mode __ro_after_init; static enum kasan_arg_vmalloc kasan_arg_vmalloc __initdata; +static enum kasan_arg_write_only kasan_arg_write_only __ro_after_init; =20 /* * Whether KASAN is enabled at all. @@ -67,6 +74,9 @@ DEFINE_STATIC_KEY_FALSE(kasan_flag_vmalloc); #endif EXPORT_SYMBOL_GPL(kasan_flag_vmalloc); =20 +/* Whether to check write accesses only. */ +static bool kasan_flag_write_only =3D false; + #define PAGE_ALLOC_SAMPLE_DEFAULT 1 #define PAGE_ALLOC_SAMPLE_ORDER_DEFAULT 3 =20 @@ -141,6 +151,23 @@ static int __init early_kasan_flag_vmalloc(char *arg) } early_param("kasan.vmalloc", early_kasan_flag_vmalloc); =20 +/* kasan.write_only=3Doff/on */ +static int __init early_kasan_flag_write_only(char *arg) +{ + if (!arg) + return -EINVAL; + + if (!strcmp(arg, "off")) + kasan_arg_write_only =3D KASAN_ARG_WRITE_ONLY_OFF; + else if (!strcmp(arg, "on")) + kasan_arg_write_only =3D KASAN_ARG_WRITE_ONLY_ON; + else + return -EINVAL; + + return 0; +} +early_param("kasan.write_only", early_kasan_flag_write_only); + static inline const char *kasan_mode_info(void) { if (kasan_mode =3D=3D KASAN_MODE_ASYNC) @@ -257,15 +284,28 @@ void __init kasan_init_hw_tags(void) break; } =20 + switch (kasan_arg_write_only) { + case KASAN_ARG_WRITE_ONLY_DEFAULT: + /* Default is specified by kasan_flag_write_only definition. */ + break; + case KASAN_ARG_WRITE_ONLY_OFF: + kasan_flag_write_only =3D false; + break; + case KASAN_ARG_WRITE_ONLY_ON: + kasan_flag_write_only =3D true; + break; + } + kasan_init_tags(); =20 /* KASAN is now initialized, enable it. */ static_branch_enable(&kasan_flag_enabled); =20 - pr_info("KernelAddressSanitizer initialized (hw-tags, mode=3D%s, vmalloc= =3D%s, stacktrace=3D%s)\n", + pr_info("KernelAddressSanitizer initialized (hw-tags, mode=3D%s, vmalloc= =3D%s, stacktrace=3D%s, write_only=3D%s)\n", kasan_mode_info(), str_on_off(kasan_vmalloc_enabled()), - str_on_off(kasan_stack_collection_enabled())); + str_on_off(kasan_stack_collection_enabled()), + str_on_off(kasan_arg_write_only)); } =20 #ifdef CONFIG_KASAN_VMALLOC @@ -392,6 +432,26 @@ void kasan_enable_hw_tags(void) hw_enable_tag_checks_asymm(); else hw_enable_tag_checks_sync(); + + /* + * CPUs can only be in one of two states: + * - All CPUs support the write_only feature + * - No CPUs support the write_only feature + * + * If the first CPU attempts hw_enable_tag_checks_write_only() and + * finds the feature unsupported, kasan_arg_write_only is set to OFF + * to avoid further unnecessary calls on other CPUs. + * + * Although this could be tracked with a single variable, both + * kasan_arg_write_only (boot argument) and kasan_flag_write_only + * (hardware state) are kept separate, consistent with other options. + */ + if (kasan_arg_write_only =3D=3D KASAN_ARG_WRITE_ONLY_ON && + hw_enable_tag_checks_write_only()) { + kasan_arg_write_only =3D KASAN_ARG_WRITE_ONLY_OFF; + kasan_flag_write_only =3D false; + pr_err_once("write-only mode is not supported and thus not enabled\n"); + } } =20 #if IS_ENABLED(CONFIG_KASAN_KUNIT_TEST) @@ -404,4 +464,10 @@ VISIBLE_IF_KUNIT void kasan_force_async_fault(void) } EXPORT_SYMBOL_IF_KUNIT(kasan_force_async_fault); =20 +VISIBLE_IF_KUNIT bool kasan_write_only_enabled(void) +{ + return kasan_flag_write_only; +} +EXPORT_SYMBOL_IF_KUNIT(kasan_write_only_enabled); + #endif diff --git a/mm/kasan/kasan.h b/mm/kasan/kasan.h index 129178be5e64..844eedf2ef9c 100644 --- a/mm/kasan/kasan.h +++ b/mm/kasan/kasan.h @@ -431,6 +431,7 @@ static inline const void *arch_kasan_set_tag(const void= *addr, u8 tag) #define hw_suppress_tag_checks_start() arch_suppress_tag_checks_start() #define hw_suppress_tag_checks_stop() arch_suppress_tag_checks_stop() #define hw_force_async_tag_fault() arch_force_async_tag_fault() +#define hw_enable_tag_checks_write_only() arch_enable_tag_checks_write_onl= y() #define hw_get_random_tag() arch_get_random_tag() #define hw_get_mem_tag(addr) arch_get_mem_tag(addr) #define hw_set_mem_tag_range(addr, size, tag, init) \ @@ -451,11 +452,17 @@ void __init kasan_init_tags(void); #if defined(CONFIG_KASAN_HW_TAGS) && IS_ENABLED(CONFIG_KASAN_KUNIT_TEST) =20 void kasan_force_async_fault(void); +bool kasan_write_only_enabled(void); =20 #else /* CONFIG_KASAN_HW_TAGS && CONFIG_KASAN_KUNIT_TEST */ =20 static inline void kasan_force_async_fault(void) { } =20 +static inline bool kasan_write_only_enabled(void) +{ + return false; +} + #endif /* CONFIG_KASAN_HW_TAGS && CONFIG_KASAN_KUNIT_TEST */ =20 #ifdef CONFIG_KASAN_SW_TAGS --=20 LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7} From nobody Fri Oct 3 07:42:45 2025 Received: from foss.arm.com (foss.arm.com [217.140.110.172]) by smtp.subspace.kernel.org (Postfix) with ESMTP id D5BA23128B1; Wed, 3 Sep 2025 15:00:49 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=217.140.110.172 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756911652; cv=none; b=CmJ/8QxB7PVNKs18wj110UqZPoDy4VprPN0vIHCLgw9nF72UD/IZtYHm2pn1AG2HboIKtM1noBKlelRiEjqJnX1a1XL+C7STEXDZAQomeIFjjBDr7PQHmlFQjbpD2He5TGSnExKBojk7Slr+dqHFkM05kBIGC9UFw3Oug2c0MSo= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1756911652; c=relaxed/simple; bh=3vBoNKVDuPK4+rRBPCwmOGoEOEMy+iqJg+vRDnTMCKQ=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=XfbG9PZQcKH4hotOJHwkwB8mNDCGVNpkG5E698/074BXJHEKo3zVqVe1+ct6OoIiPOMcrEFyU3YvXWutnpAbu3lSMtSbKD/nVyj98iFYFdmDIoweGM1ctuZQCIsAjpsgoyC5/+IXtwVKTz6mgEJ/A6lZawdWNfnSfdAkrNJhMNA= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com; spf=pass smtp.mailfrom=arm.com; arc=none smtp.client-ip=217.140.110.172 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=arm.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=arm.com Received: from usa-sjc-imap-foss1.foss.arm.com (unknown [10.121.207.14]) by usa-sjc-mx-foss1.foss.arm.com (Postfix) with ESMTP id BB6AE1688; Wed, 3 Sep 2025 08:00:40 -0700 (PDT) Received: from e129823.cambridge.arm.com (e129823.arm.com [10.1.197.6]) by usa-sjc-imap-foss1.foss.arm.com (Postfix) with ESMTPA id 1A8EF3F694; Wed, 3 Sep 2025 08:00:44 -0700 (PDT) From: Yeoreum Yun To: ryabinin.a.a@gmail.com, glider@google.com, andreyknvl@gmail.com, dvyukov@google.com, vincenzo.frascino@arm.com, corbet@lwn.net, catalin.marinas@arm.com, will@kernel.org, akpm@linux-foundation.org, scott@os.amperecomputing.com, jhubbard@nvidia.com, pankaj.gupta@amd.com, leitao@debian.org, kaleshsingh@google.com, maz@kernel.org, broonie@kernel.org, oliver.upton@linux.dev, james.morse@arm.com, ardb@kernel.org, hardevsinh.palaniya@siliconsignals.io, david@redhat.com, yang@os.amperecomputing.com Cc: kasan-dev@googlegroups.com, workflows@vger.kernel.org, linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org, linux-arm-kernel@lists.infradead.org, linux-mm@kvack.org, Yeoreum Yun Subject: [PATCH v7 2/2] kasan: apply write-only mode in kasan kunit testcases Date: Wed, 3 Sep 2025 16:00:20 +0100 Message-Id: <20250903150020.1131840-3-yeoreum.yun@arm.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20250903150020.1131840-1-yeoreum.yun@arm.com> References: <20250903150020.1131840-1-yeoreum.yun@arm.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: quoted-printable Content-Type: text/plain; charset="utf-8" When KASAN is configured in write-only mode, fetch/load operations do not trigger tag check faults. As a result, the outcome of some test cases may differ compared to when KASAN is configured without write-only mode. Therefore, by modifying pre-exist testcases check the write only makes tag check fault (TCF) where writing is perform in "allocated memory" but tag is invalid (i.e) redzone write in atomic_set() testcases. Otherwise check the invalid fetch/read doesn't generate TCF. Also, skip some testcases affected by initial value (i.e) atomic_cmpxchg() testcase maybe successd if it passes valid atomic_t address and invalid oldaval address. In this case, if invalid atomic_t doesn't have the same oldval, it won't trigger write operation so the test will pass. Signed-off-by: Yeoreum Yun Reviewed-by: Andrey Konovalov --- mm/kasan/kasan_test_c.c | 205 ++++++++++++++++++++++++++-------------- 1 file changed, 136 insertions(+), 69 deletions(-) diff --git a/mm/kasan/kasan_test_c.c b/mm/kasan/kasan_test_c.c index f4b17984b627..484fbbc8b55e 100644 --- a/mm/kasan/kasan_test_c.c +++ b/mm/kasan/kasan_test_c.c @@ -94,11 +94,14 @@ static void kasan_test_exit(struct kunit *test) } =20 /** - * KUNIT_EXPECT_KASAN_FAIL - check that the executed expression produces a - * KASAN report; causes a KUnit test failure otherwise. + * KUNIT_EXPECT_KASAN_RESULT - checks whether the executed expression + * produces a KASAN report; causes a KUnit test failure when the result + * is different from @fail. * * @test: Currently executing KUnit test. - * @expression: Expression that must produce a KASAN report. + * @expr: Expression to be tested. + * @expr_str: Expression to be tested encoded as a string. + * @fail: Whether expression should produce a KASAN report. * * For hardware tag-based KASAN, when a synchronous tag fault happens, tag * checking is auto-disabled. When this happens, this test handler reenabl= es @@ -110,25 +113,29 @@ static void kasan_test_exit(struct kunit *test) * Use READ/WRITE_ONCE() for the accesses and compiler barriers around the * expression to prevent that. * - * In between KUNIT_EXPECT_KASAN_FAIL checks, test_status.report_found is = kept + * In between KUNIT_EXPECT_KASAN_RESULT checks, test_status.report_found i= s kept * as false. This allows detecting KASAN reports that happen outside of the * checks by asserting !test_status.report_found at the start of - * KUNIT_EXPECT_KASAN_FAIL and in kasan_test_exit. + * KUNIT_EXPECT_KASAN_RESULT and in kasan_test_exit. */ -#define KUNIT_EXPECT_KASAN_FAIL(test, expression) do { \ +#define KUNIT_EXPECT_KASAN_RESULT(test, expr, expr_str, fail) \ +do { \ if (IS_ENABLED(CONFIG_KASAN_HW_TAGS) && \ kasan_sync_fault_possible()) \ migrate_disable(); \ KUNIT_EXPECT_FALSE(test, READ_ONCE(test_status.report_found)); \ barrier(); \ - expression; \ + expr; \ barrier(); \ if (kasan_async_fault_possible()) \ kasan_force_async_fault(); \ - if (!READ_ONCE(test_status.report_found)) { \ - KUNIT_FAIL(test, KUNIT_SUBTEST_INDENT "KASAN failure " \ - "expected in \"" #expression \ - "\", but none occurred"); \ + if (READ_ONCE(test_status.report_found) !=3D fail) { \ + KUNIT_FAIL(test, KUNIT_SUBTEST_INDENT "KASAN failure" \ + "%sexpected in \"" expr_str \ + "\", but %soccurred", \ + (fail ? " " : " not "), \ + (test_status.report_found ? \ + "" : "none ")); \ } \ if (IS_ENABLED(CONFIG_KASAN_HW_TAGS) && \ kasan_sync_fault_possible()) { \ @@ -141,6 +148,34 @@ static void kasan_test_exit(struct kunit *test) WRITE_ONCE(test_status.async_fault, false); \ } while (0) =20 +/* + * KUNIT_EXPECT_KASAN_FAIL - check that the executed expression produces a + * KASAN report; causes a KUnit test failure otherwise. + * + * @test: Currently executing KUnit test. + * @expr: Expression that must produce a KASAN report. + */ +#define KUNIT_EXPECT_KASAN_FAIL(test, expr) \ + KUNIT_EXPECT_KASAN_RESULT(test, expr, #expr, true) + +/* + * KUNIT_EXPECT_KASAN_FAIL_READ - check that the executed expression + * produces a KASAN report when the write-only mode is not enabled; + * causes a KUnit test failure otherwise. + * + * Note: At the moment, this macro does not check whether the produced + * KASAN report is a report about a bad read access. It is only intended + * for checking the write-only KASAN mode functionality without failing + * KASAN tests. + * + * @test: Currently executing KUnit test. + * @expr: Expression that must only produce a KASAN report + * when the write-only mode is not enabled. + */ +#define KUNIT_EXPECT_KASAN_FAIL_READ(test, expr) \ + KUNIT_EXPECT_KASAN_RESULT(test, expr, #expr, \ + !kasan_write_only_enabled()) \ + #define KASAN_TEST_NEEDS_CONFIG_ON(test, config) do { \ if (!IS_ENABLED(config)) \ kunit_skip((test), "Test requires " #config "=3Dy"); \ @@ -183,8 +218,8 @@ static void kmalloc_oob_right(struct kunit *test) KUNIT_EXPECT_KASAN_FAIL(test, ptr[size + 5] =3D 'y'); =20 /* Out-of-bounds access past the aligned kmalloc object. */ - KUNIT_EXPECT_KASAN_FAIL(test, ptr[0] =3D - ptr[size + KASAN_GRANULE_SIZE + 5]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ptr[0] =3D + ptr[size + KASAN_GRANULE_SIZE + 5]); =20 kfree(ptr); } @@ -198,7 +233,7 @@ static void kmalloc_oob_left(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); =20 OPTIMIZER_HIDE_VAR(ptr); - KUNIT_EXPECT_KASAN_FAIL(test, *ptr =3D *(ptr - 1)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, *ptr =3D *(ptr - 1)); kfree(ptr); } =20 @@ -211,7 +246,7 @@ static void kmalloc_node_oob_right(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); =20 OPTIMIZER_HIDE_VAR(ptr); - KUNIT_EXPECT_KASAN_FAIL(test, ptr[0] =3D ptr[size]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ptr[0] =3D ptr[size]); kfree(ptr); } =20 @@ -291,7 +326,7 @@ static void kmalloc_large_uaf(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); kfree(ptr); =20 - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[0]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[0]); } =20 static void kmalloc_large_invalid_free(struct kunit *test) @@ -323,7 +358,7 @@ static void page_alloc_oob_right(struct kunit *test) ptr =3D page_address(pages); KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); =20 - KUNIT_EXPECT_KASAN_FAIL(test, ptr[0] =3D ptr[size]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ptr[0] =3D ptr[size]); free_pages((unsigned long)ptr, order); } =20 @@ -338,7 +373,7 @@ static void page_alloc_uaf(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); free_pages((unsigned long)ptr, order); =20 - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[0]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[0]); } =20 static void krealloc_more_oob_helper(struct kunit *test, @@ -458,7 +493,7 @@ static void krealloc_uaf(struct kunit *test) =20 KUNIT_EXPECT_KASAN_FAIL(test, ptr2 =3D krealloc(ptr1, size2, GFP_KERNEL)); KUNIT_ASSERT_NULL(test, ptr2); - KUNIT_EXPECT_KASAN_FAIL(test, *(volatile char *)ptr1); + KUNIT_EXPECT_KASAN_FAIL_READ(test, *(volatile char *)ptr1); } =20 static void kmalloc_oob_16(struct kunit *test) @@ -501,7 +536,7 @@ static void kmalloc_uaf_16(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr2); kfree(ptr2); =20 - KUNIT_EXPECT_KASAN_FAIL(test, *ptr1 =3D *ptr2); + KUNIT_EXPECT_KASAN_FAIL_READ(test, *ptr1 =3D *ptr2); kfree(ptr1); } =20 @@ -640,8 +675,8 @@ static void kmalloc_memmove_invalid_size(struct kunit *= test) memset((char *)ptr, 0, 64); OPTIMIZER_HIDE_VAR(ptr); OPTIMIZER_HIDE_VAR(invalid_size); - KUNIT_EXPECT_KASAN_FAIL(test, - memmove((char *)ptr, (char *)ptr + 4, invalid_size)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, + memmove((char *)ptr, (char *)ptr + 4, invalid_size)); kfree(ptr); } =20 @@ -654,7 +689,7 @@ static void kmalloc_uaf(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr); =20 kfree(ptr); - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[8]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[8]); } =20 static void kmalloc_uaf_memset(struct kunit *test) @@ -701,7 +736,7 @@ static void kmalloc_uaf2(struct kunit *test) goto again; } =20 - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr1)[40]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr1)[40]); KUNIT_EXPECT_PTR_NE(test, ptr1, ptr2); =20 kfree(ptr2); @@ -727,19 +762,19 @@ static void kmalloc_uaf3(struct kunit *test) KUNIT_ASSERT_NOT_ERR_OR_NULL(test, ptr2); kfree(ptr2); =20 - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr1)[8]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr1)[8]); } =20 static void kasan_atomics_helper(struct kunit *test, void *unsafe, void *s= afe) { int *i_unsafe =3D unsafe; =20 - KUNIT_EXPECT_KASAN_FAIL(test, READ_ONCE(*i_unsafe)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, READ_ONCE(*i_unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, WRITE_ONCE(*i_unsafe, 42)); - KUNIT_EXPECT_KASAN_FAIL(test, smp_load_acquire(i_unsafe)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, smp_load_acquire(i_unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, smp_store_release(i_unsafe, 42)); =20 - KUNIT_EXPECT_KASAN_FAIL(test, atomic_read(unsafe)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, atomic_read(unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_set(unsafe, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_add(42, unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_sub(42, unsafe)); @@ -752,18 +787,31 @@ static void kasan_atomics_helper(struct kunit *test, = void *unsafe, void *safe) KUNIT_EXPECT_KASAN_FAIL(test, atomic_xchg(unsafe, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_cmpxchg(unsafe, 21, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_try_cmpxchg(unsafe, safe, 42)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_try_cmpxchg(safe, unsafe, 42)); + /* + * The result of the test below may vary due to garbage values of + * unsafe in write-only mode. + * Therefore, skip this test when KASAN is configured in write-only mode. + */ + if (!kasan_write_only_enabled()) + KUNIT_EXPECT_KASAN_FAIL(test, atomic_try_cmpxchg(safe, unsafe, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_sub_and_test(42, unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_dec_and_test(unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_inc_and_test(unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_add_negative(42, unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_add_unless(unsafe, 21, 42)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_inc_not_zero(unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_inc_unless_negative(unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_dec_unless_positive(unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_dec_if_positive(unsafe)); + /* + * The result of the test below may vary due to garbage values of + * unsafe in write-only mode. + * Therefore, skip this test when KASAN is configured in write-only mode. + */ + if (!kasan_write_only_enabled()) { + KUNIT_EXPECT_KASAN_FAIL(test, atomic_add_unless(unsafe, 21, 42)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_inc_not_zero(unsafe)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_inc_unless_negative(unsafe)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_dec_unless_positive(unsafe)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_dec_if_positive(unsafe)); + } =20 - KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_read(unsafe)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, atomic_long_read(unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_set(unsafe, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_add(42, unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_sub(42, unsafe)); @@ -776,16 +824,29 @@ static void kasan_atomics_helper(struct kunit *test, = void *unsafe, void *safe) KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_xchg(unsafe, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_cmpxchg(unsafe, 21, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_try_cmpxchg(unsafe, safe, 42)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_try_cmpxchg(safe, unsafe, 42)); + /* + * The result of the test below may vary due to garbage values of + * unsafe in write-only mode. + * Therefore, skip this test when KASAN is configured in write-only mode. + */ + if (!kasan_write_only_enabled()) + KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_try_cmpxchg(safe, unsafe, 42)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_sub_and_test(42, unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_dec_and_test(unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_inc_and_test(unsafe)); KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_add_negative(42, unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_add_unless(unsafe, 21, 42)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_inc_not_zero(unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_inc_unless_negative(unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_dec_unless_positive(unsafe)); - KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_dec_if_positive(unsafe)); + /* + * The result of the test below may vary due to garbage values of + * unsafe in write-only mode. + * Therefore, skip this test when KASAN is configured in write-only mode. + */ + if (!kasan_write_only_enabled()) { + KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_add_unless(unsafe, 21, 42)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_inc_not_zero(unsafe)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_inc_unless_negative(unsafe)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_dec_unless_positive(unsafe)); + KUNIT_EXPECT_KASAN_FAIL(test, atomic_long_dec_if_positive(unsafe)); + } } =20 static void kasan_atomics(struct kunit *test) @@ -842,8 +903,8 @@ static void ksize_unpoisons_memory(struct kunit *test) /* These must trigger a KASAN report. */ if (IS_ENABLED(CONFIG_KASAN_GENERIC)) KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[size]); - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[size + 5]); - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[real_size - 1]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[size + 5]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[real_size - 1]); =20 kfree(ptr); } @@ -863,8 +924,8 @@ static void ksize_uaf(struct kunit *test) =20 OPTIMIZER_HIDE_VAR(ptr); KUNIT_EXPECT_KASAN_FAIL(test, ksize(ptr)); - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[0]); - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[size]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[0]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[size]); } =20 /* @@ -899,9 +960,9 @@ static void rcu_uaf(struct kunit *test) global_rcu_ptr =3D rcu_dereference_protected( (struct kasan_rcu_info __rcu *)ptr, NULL); =20 - KUNIT_EXPECT_KASAN_FAIL(test, - call_rcu(&global_rcu_ptr->rcu, rcu_uaf_reclaim); - rcu_barrier()); + KUNIT_EXPECT_KASAN_FAIL_READ(test, + call_rcu(&global_rcu_ptr->rcu, rcu_uaf_reclaim); + rcu_barrier()); } =20 static void workqueue_uaf_work(struct work_struct *work) @@ -924,8 +985,8 @@ static void workqueue_uaf(struct kunit *test) queue_work(workqueue, work); destroy_workqueue(workqueue); =20 - KUNIT_EXPECT_KASAN_FAIL(test, - ((volatile struct work_struct *)work)->data); + KUNIT_EXPECT_KASAN_FAIL_READ(test, + ((volatile struct work_struct *)work)->data); } =20 static void kfree_via_page(struct kunit *test) @@ -972,7 +1033,7 @@ static void kmem_cache_oob(struct kunit *test) return; } =20 - KUNIT_EXPECT_KASAN_FAIL(test, *p =3D p[size + OOB_TAG_OFF]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, *p =3D p[size + OOB_TAG_OFF]); =20 kmem_cache_free(cache, p); kmem_cache_destroy(cache); @@ -1068,7 +1129,7 @@ static void kmem_cache_rcu_uaf(struct kunit *test) */ rcu_barrier(); =20 - KUNIT_EXPECT_KASAN_FAIL(test, READ_ONCE(*p)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, READ_ONCE(*p)); =20 kmem_cache_destroy(cache); } @@ -1207,7 +1268,7 @@ static void mempool_oob_right_helper(struct kunit *te= st, mempool_t *pool, size_t KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)&elem[size])[0]); else - KUNIT_EXPECT_KASAN_FAIL(test, + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)&elem[round_up(size, KASAN_GRANULE_SIZE)])[0]); =20 mempool_free(elem, pool); @@ -1273,7 +1334,7 @@ static void mempool_uaf_helper(struct kunit *test, me= mpool_t *pool, bool page) mempool_free(elem, pool); =20 ptr =3D page ? page_address((struct page *)elem) : elem; - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)ptr)[0]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)ptr)[0]); } =20 static void mempool_kmalloc_uaf(struct kunit *test) @@ -1532,7 +1593,7 @@ static void kasan_memchr(struct kunit *test) =20 OPTIMIZER_HIDE_VAR(ptr); OPTIMIZER_HIDE_VAR(size); - KUNIT_EXPECT_KASAN_FAIL(test, + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_ptr_result =3D memchr(ptr, '1', size + 1)); =20 kfree(ptr); @@ -1559,7 +1620,7 @@ static void kasan_memcmp(struct kunit *test) =20 OPTIMIZER_HIDE_VAR(ptr); OPTIMIZER_HIDE_VAR(size); - KUNIT_EXPECT_KASAN_FAIL(test, + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_int_result =3D memcmp(ptr, arr, size+1)); kfree(ptr); } @@ -1596,7 +1657,7 @@ static void kasan_strings(struct kunit *test) strscpy(ptr, src + 1, KASAN_GRANULE_SIZE)); =20 /* strscpy should fail if the first byte is unreadable. */ - KUNIT_EXPECT_KASAN_FAIL(test, strscpy(ptr, src + KASAN_GRANULE_SIZE, + KUNIT_EXPECT_KASAN_FAIL_READ(test, strscpy(ptr, src + KASAN_GRANULE_SIZE, KASAN_GRANULE_SIZE)); =20 kfree(src); @@ -1609,17 +1670,17 @@ static void kasan_strings(struct kunit *test) * will likely point to zeroed byte. */ ptr +=3D 16; - KUNIT_EXPECT_KASAN_FAIL(test, kasan_ptr_result =3D strchr(ptr, '1')); + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_ptr_result =3D strchr(ptr, '1')); =20 - KUNIT_EXPECT_KASAN_FAIL(test, kasan_ptr_result =3D strrchr(ptr, '1')); + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_ptr_result =3D strrchr(ptr, '1')= ); =20 - KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result =3D strcmp(ptr, "2")); + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_int_result =3D strcmp(ptr, "2")); =20 - KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result =3D strncmp(ptr, "2", 1)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_int_result =3D strncmp(ptr, "2",= 1)); =20 - KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result =3D strlen(ptr)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_int_result =3D strlen(ptr)); =20 - KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result =3D strnlen(ptr, 1)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_int_result =3D strnlen(ptr, 1)); } =20 static void kasan_bitops_modify(struct kunit *test, int nr, void *addr) @@ -1638,12 +1699,18 @@ static void kasan_bitops_test_and_modify(struct kun= it *test, int nr, void *addr) { KUNIT_EXPECT_KASAN_FAIL(test, test_and_set_bit(nr, addr)); KUNIT_EXPECT_KASAN_FAIL(test, __test_and_set_bit(nr, addr)); - KUNIT_EXPECT_KASAN_FAIL(test, test_and_set_bit_lock(nr, addr)); + /* + * When KASAN is running in write-only mode, + * a fault won't occur when the bit is set. + * Therefore, skip the test_and_set_bit_lock test in write-only mode. + */ + if (!kasan_write_only_enabled()) + KUNIT_EXPECT_KASAN_FAIL(test, test_and_set_bit_lock(nr, addr)); KUNIT_EXPECT_KASAN_FAIL(test, test_and_clear_bit(nr, addr)); KUNIT_EXPECT_KASAN_FAIL(test, __test_and_clear_bit(nr, addr)); KUNIT_EXPECT_KASAN_FAIL(test, test_and_change_bit(nr, addr)); KUNIT_EXPECT_KASAN_FAIL(test, __test_and_change_bit(nr, addr)); - KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result =3D test_bit(nr, addr)); + KUNIT_EXPECT_KASAN_FAIL_READ(test, kasan_int_result =3D test_bit(nr, addr= )); if (nr < 7) KUNIT_EXPECT_KASAN_FAIL(test, kasan_int_result =3D xor_unlock_is_negative_byte(1 << nr, addr)); @@ -1767,7 +1834,7 @@ static void vmalloc_oob(struct kunit *test) KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)v_ptr)[size]); =20 /* An aligned access into the first out-of-bounds granule. */ - KUNIT_EXPECT_KASAN_FAIL(test, ((volatile char *)v_ptr)[size + 5]); + KUNIT_EXPECT_KASAN_FAIL_READ(test, ((volatile char *)v_ptr)[size + 5]); =20 /* Check that in-bounds accesses to the physical page are valid. */ page =3D vmalloc_to_page(v_ptr); @@ -2044,15 +2111,15 @@ static void copy_user_test_oob(struct kunit *test) =20 KUNIT_EXPECT_KASAN_FAIL(test, unused =3D copy_from_user(kmem, usermem, size + 1)); - KUNIT_EXPECT_KASAN_FAIL(test, + KUNIT_EXPECT_KASAN_FAIL_READ(test, unused =3D copy_to_user(usermem, kmem, size + 1)); KUNIT_EXPECT_KASAN_FAIL(test, unused =3D __copy_from_user(kmem, usermem, size + 1)); - KUNIT_EXPECT_KASAN_FAIL(test, + KUNIT_EXPECT_KASAN_FAIL_READ(test, unused =3D __copy_to_user(usermem, kmem, size + 1)); KUNIT_EXPECT_KASAN_FAIL(test, unused =3D __copy_from_user_inatomic(kmem, usermem, size + 1)); - KUNIT_EXPECT_KASAN_FAIL(test, + KUNIT_EXPECT_KASAN_FAIL_READ(test, unused =3D __copy_to_user_inatomic(usermem, kmem, size + 1)); =20 /* --=20 LEVI:{C3F47F37-75D8-414A-A8BA-3980EC8A46D7}