Skip to contentSkip to conversation

kasan: Fix false-positive wild-memory-access on x86 under 5-level paging

PATCH In review Ihor Solodrai 8 participants 19 messages Jun 10 – Oct 12

Subsystems (5)
Memory management x86 BPF x86 MM +1 more subsystems
lore
Share & tools
lore

Ihor Solodrai

author reviewer
#0 lore raw

PATCH 1/1 kasan: Fix false-positive wild-memory-access on x86 under 5-level paging

On x86_64 with 5-level paging (LA57) and inline generic KASAN, the following flaky splat may be observed on boot:

    BUG: KASAN: wild-memory-access in do_raw_spin_lock+0xcf/0x260
    Write of size 4 at addr ff110001000c90b8 by task swapper/0/0

    CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.1.0-rc5-gcba33e0b2907 #1 PREEMPT(full)
    Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
    Call Trace:
     <IRQ>
     dump_stack_lvl+0x54/0x70
     kasan_report+0x117/0x150
     ? do_raw_spin_lock+0xcf/0x260
     kasan_check_range+0x264/0x2c0
     do_raw_spin_lock+0xcf/0x260
     handle_edge_irq+0x35/0x770
     ? do_raw_spin_unlock+0x51/0x2a0
     __common_interrupt+0xae/0x120
     common_interrupt+0x7c/0x90
     </IRQ>
     <TASK>
     asm_common_interrupt+0x26/0x40
    RIP: 0010:identify_cpu+0x2b2/0x3460
    Code: 00 41 c7 07 00 00 00 00 4d 89 e6 49 c1 ee 03 43 0f b6 04 06 84 c0 0f 85 a3 1c 00 00 41 c7 04 24 00 00 00 00 31 c0 31 c9 0f a2 <89> c7 42 0f b6 44 05 00 84 c0 0f 85 ad 1c 00 00 41 89 3f 48 8b 44
    RSP: 0000:ffffffff97807df0 EFLAGS: 00000246
    RAX: 0000000000000020 RBX: 00000000756e6547 RCX: 000000006c65746e
    RDX: 0000000049656e69 RSI: 0000000000000000 RDI: ffffffff98632fd8
    RBP: 1ffffffff30c65fc R08: dffffc0000000000 R09: 0000000000000004
    R10: ffffffff98632fc4 R11: fffffbfff30c65fb R12: ffffffff98633050
    R13: ffffffff98633048 R14: 1ffffffff30c660a R15: ffffffff98632fe0
     identify_boot_cpu+0xd/0xd0
     arch_cpu_finalize_init+0x24/0x1f0
     start_kernel+0x31e/0x3e0
     x86_64_start_reservations+0x24/0x30
     x86_64_start_kernel+0x13a/0x140
     common_startup_64+0x12c/0x137
     </TASK>

It fires very early in boot. If kasan_multi_shot is set, the reports are non-fatal and keep repeating, and the boot CPU wedges before userspace is reached. The accessed addresses are valid 5-level kernel pointers, so the report is a false positive.

The root cause is in generic KASAN not seeing cpu_feature_enabled(X86_FEATURE_LA57) set, because the bit is cleared in identify_cpu() when the offending interrupt happens [1]:

memset(&c->x86_capability, 0, ...); /* clears X86_FEATURE_LA57 */
...
get_cpu_cap(c); /* re-reads CPUID, restores it */

addr_has_metadata() then uses the 4-level threshold, and 5-level kernel addresses fall below it, so kasan_check_range() reports them as wild-memory-access.

Define USE_EARLY_PGTABLE_L5 in mm/kasan/generic.c so addr_has_metadata() uses the stable variable, as arch/x86/mm/kasan_init_64.c already does.

Some context on how this was noticed and reproduced below.

We started seeing flaky splats as above [2][3] on BPF CI runs after runner hardware has been upgraded. Specifically, new x86 runners are c7i.metal-24xl AWS EC2 instances, which are Intel Sapphire Rapids machines that support LA57 feature, and have it enabled.

The splats can be reproduced with qemu on any x86_64 host with
-cpu max -accel tcg

Build a kernel with:
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KASAN_INLINE=y

Boot it with kasan_multi_shot. The fault fires fast before userspace, so no rootfs is required. For example:

  qemu-system-x86_64 -display none -serial stdio -no-reboot \
    -smp 4 -m 5G -cpu max -accel tcg \
    -kernel arch/x86/boot/bzImage \
    -append "console=ttyS0,115200 earlyprintk=serial,0,115200 panic=-1 kasan_multi_shot nokaslr"

It's a timing race, so a single boot hits it only sometimes. However running several qemu instances in parallel on the same host significantly increases the hitrate.

I confirmed the proposed fix eliminates the splats.

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c?h=v7.1-rc7#n2001
[2] https://github.com/kernel-patches/bpf/actions/runs/27271262414/job/80542509369
[3] https://github.com/kernel-patches/bpf/actions/runs/27260143782/job/80505353689

---
 mm/kasan/generic.c | 7 +++++++
 1 file changed, 7 insertions(+)
mm/kasan/generic.c +7 -0
index 2b8e73f5f6a7..b5f430f2dbb6 100644
@@ -9,6 +9,13 @@
  *        Andrey Konovalov <[email protected]>
  */
 
+/*
+ * check_region_inline() and addr_has_metadata() can run very early.
+ * For example, in an interrupt taken while identify_cpu() has the CPU
+ * capability bits temporarily cleared.
+ */
+#define USE_EARLY_PGTABLE_L5
+
 #include <linux/export.h>
 #include <linux/interrupt.h>
 #include <linux/init.h>
Signature · 1 line
2.54.0
#1 lore raw
Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Medium] Incomplete fix and architectural layering violation via x86-specific macro in generic KASAN code.

Pre-existing issues:
- [High] Unhandled root cause: CPU feature flags are non-atomically cleared while interrupts are enabled. --

commit 5a28adbdf7cd811d1903c7692ad3799adc4606f3
Author: Ihor Solodrai <[email protected]>

kasan: Fix false-positive wild-memory-access on x86 under 5-level paging

This commit attempts to fix a flaky false-positive KASAN splat on x86_64 with 5-level paging. It addresses a race where the LA57 CPU feature is temporarily cleared during boot by defining USE_EARLY_PGTABLE_L5 in the generic KASAN code.

Quoted 5 lines
mm/kasan/generic.c +0 -0
index 2b8e73f5f6a70..b5f430f2dbb68 100644

mm/kasan/generic.c line 17

Quoted 9 lines
mm/kasan/generic.c +6 -0
@@ -9,6 +9,13 @@
  *        Andrey Konovalov <[email protected]>
  */
 
+/*
+ * check_region_inline() and addr_has_metadata() can run very early.
+ * For example, in an interrupt taken while identify_cpu() has the CPU
+ * capability bits temporarily cleared.
+ */
+#define USE_EARLY_PGTABLE_L5

[Severity: Medium]
Is it appropriate to place the x86-specific USE_EARLY_PGTABLE_L5 macro in the generic KASAN code?

Since this macro is only defined locally in generic.c, other generic KASAN files like mm/kasan/report.c remain unaware of it.

If a genuine KASAN bug occurs during the race window, wouldn't print_report() miscalculate the shadow memory addresses:

mm/kasan/report.c:print_report() {
    ...
    if (addr_has_metadata(addr)) {
    ...
}

and fail to print the memory metadata because it uses the 4-level paging start address instead of the 5-level one?

[ ... ]

Quoted 7 lines

The root cause is in generic KASAN not seeing cpu_feature_enabled(X86_FEATURE_LA57) set, because the bit is cleared in identify_cpu() when the offending interrupt happens [1]:

memset(&c->x86_capability, 0, ...); /* clears X86_FEATURE_LA57 */
...
get_cpu_cap(c); /* re-reads CPUID, restores it */

[Severity: High]
This is a pre-existing issue, but does this approach leave the underlying race condition unhandled?

In arch/x86/kernel/cpu/common.c:identify_cpu(), boot_cpu_data.x86_capability is cleared non-atomically while local interrupts are still enabled. This can occur because arch_cpu_finalize_init() runs after local_irq_enable().

Because alternative instructions have not yet been patched at this point in the boot sequence, cpu_feature_enabled() dynamically checks boot_cpu_data.x86_capability.

Could any interrupt handler that fires during this window and checks a CPU feature spuriously see it as disabled, potentially causing core subsystems to execute incorrect fallback paths?

Would it be safer to disable local interrupts during the boot_cpu_data initialization in identify_cpu() instead?

Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=1
author reviewer
#2 lore raw
Quoted 38 lines · On 6/10/26 11:17 AM, [email protected] wrote:

Thank you for your contribution! Sashiko AI review found 2 potential issue(s) to consider:

New issues:
- [Medium] Incomplete fix and architectural layering violation via x86-specific macro in generic KASAN code.

Pre-existing issues:
- [High] Unhandled root cause: CPU feature flags are non-atomically cleared while interrupts are enabled. --

commit 5a28adbdf7cd811d1903c7692ad3799adc4606f3
Author: Ihor Solodrai <[email protected]>

kasan: Fix false-positive wild-memory-access on x86 under 5-level paging

This commit attempts to fix a flaky false-positive KASAN splat on x86_64 with 5-level paging. It addresses a race where the LA57 CPU feature is temporarily cleared during boot by defining USE_EARLY_PGTABLE_L5 in the generic KASAN code.

Quoted 5 lines
mm/kasan/generic.c +0 -0
index 2b8e73f5f6a70..b5f430f2dbb68 100644

mm/kasan/generic.c line 17

Quoted 9 lines
mm/kasan/generic.c +6 -0
@@ -9,6 +9,13 @@
  *        Andrey Konovalov <[email protected]>
  */
 
+/*
+ * check_region_inline() and addr_has_metadata() can run very early.
+ * For example, in an interrupt taken while identify_cpu() has the CPU
+ * capability bits temporarily cleared.
+ */
+#define USE_EARLY_PGTABLE_L5

[Severity: Medium]
Is it appropriate to place the x86-specific USE_EARLY_PGTABLE_L5 macro in the generic KASAN code?

Quoted 2 lines

Since this macro is only defined locally in generic.c, other generic KASAN files like mm/kasan/report.c remain unaware of it.

Correct. Setting USE_EARLY_PGTABLE_L5 only fixes the particular issue that I stumbled on. AFAIU there may be a slight runtime performance hit when using this flag, so we might not want to set it more widely.

I'll defer to KASAN maintainers to comment on this.

Quoted 19 lines

If a genuine KASAN bug occurs during the race window, wouldn't print_report() miscalculate the shadow memory addresses:

mm/kasan/report.c:print_report() {
    ...
    if (addr_has_metadata(addr)) {
    ...
}

and fail to print the memory metadata because it uses the 4-level paging start address instead of the 5-level one?

[ ... ]

Quoted 4 lines

The root cause is in generic KASAN not seeing cpu_feature_enabled(X86_FEATURE_LA57) set, because the bit is cleared in identify_cpu() when the offending interrupt happens [1]:

Quoted 7 lines
Quoted 3 lines

memset(&c->x86_capability, 0, ...); /* clears X86_FEATURE_LA57 */
...
get_cpu_cap(c); /* re-reads CPUID, restores it */

[Severity: High]
This is a pre-existing issue, but does this approach leave the underlying race condition unhandled?

Yes, the underlying caveat that x86_capability is cleared within an interruptable window remains. Potentially any other interrupt handler that checks for any other CPU capability may run into similar problem.

That said, LA57 and KASAN feel like a special case. And I don't see an obvious way to fix this on identify_cpu() side. Disabling IRQ is clearly an overkill.

I'm very open to alternative solutions though, I am a tourist here.

Quoted 16 lines

In arch/x86/kernel/cpu/common.c:identify_cpu(), boot_cpu_data.x86_capability is cleared non-atomically while local interrupts are still enabled. This can occur because arch_cpu_finalize_init() runs after local_irq_enable().

Because alternative instructions have not yet been patched at this point in the boot sequence, cpu_feature_enabled() dynamically checks boot_cpu_data.x86_capability.

Could any interrupt handler that fires during this window and checks a CPU feature spuriously see it as disabled, potentially causing core subsystems to execute incorrect fallback paths?

Would it be safer to disable local interrupts during the boot_cpu_data initialization in identify_cpu() instead?

#3 lore raw
In reply to Ihor Solodrai
Quoted 52 lines · Ihor Solodrai

On x86_64 with 5-level paging (LA57) and inline generic KASAN, the following flaky splat may be observed on boot:

    BUG: KASAN: wild-memory-access in do_raw_spin_lock+0xcf/0x260
    Write of size 4 at addr ff110001000c90b8 by task swapper/0/0

    CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.1.0-rc5-gcba33e0b2907 #1 PREEMPT(full)
    Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
    Call Trace:
     <IRQ>
     dump_stack_lvl+0x54/0x70
     kasan_report+0x117/0x150
     ? do_raw_spin_lock+0xcf/0x260
     kasan_check_range+0x264/0x2c0
     do_raw_spin_lock+0xcf/0x260
     handle_edge_irq+0x35/0x770
     ? do_raw_spin_unlock+0x51/0x2a0
     __common_interrupt+0xae/0x120
     common_interrupt+0x7c/0x90
     </IRQ>
     <TASK>
     asm_common_interrupt+0x26/0x40
    RIP: 0010:identify_cpu+0x2b2/0x3460
    Code: 00 41 c7 07 00 00 00 00 4d 89 e6 49 c1 ee 03 43 0f b6 04 06 84 c0 0f 85 a3 1c 00 00 41 c7 04 24 00 00 00 00 31 c0 31 c9 0f a2 <89> c7 42 0f b6 44 05 00 84 c0 0f 85 ad 1c 00 00 41 89 3f 48 8b 44
    RSP: 0000:ffffffff97807df0 EFLAGS: 00000246
    RAX: 0000000000000020 RBX: 00000000756e6547 RCX: 000000006c65746e
    RDX: 0000000049656e69 RSI: 0000000000000000 RDI: ffffffff98632fd8
    RBP: 1ffffffff30c65fc R08: dffffc0000000000 R09: 0000000000000004
    R10: ffffffff98632fc4 R11: fffffbfff30c65fb R12: ffffffff98633050
    R13: ffffffff98633048 R14: 1ffffffff30c660a R15: ffffffff98632fe0
     identify_boot_cpu+0xd/0xd0
     arch_cpu_finalize_init+0x24/0x1f0
     start_kernel+0x31e/0x3e0
     x86_64_start_reservations+0x24/0x30
     x86_64_start_kernel+0x13a/0x140
     common_startup_64+0x12c/0x137
     </TASK>

It fires very early in boot. If kasan_multi_shot is set, the reports are non-fatal and keep repeating, and the boot CPU wedges before userspace is reached. The accessed addresses are valid 5-level kernel pointers, so the report is a false positive.

The root cause is in generic KASAN not seeing cpu_feature_enabled(X86_FEATURE_LA57) set, because the bit is cleared in identify_cpu() when the offending interrupt happens [1]:

memset(&c->x86_capability, 0, ...); /* clears X86_FEATURE_LA57 */
...
get_cpu_cap(c); /* re-reads CPUID, restores it */

Quoted 7 lines

addr_has_metadata() then uses the 4-level threshold, and 5-level kernel addresses fall below it, so kasan_check_range() reports them as wild-memory-access.

Define USE_EARLY_PGTABLE_L5 in mm/kasan/generic.c so addr_has_metadata() uses the stable variable, as arch/x86/mm/kasan_init_64.c already does.

It took me some digging to find which stable variable you refer to here; I think this needs to be better explained.

Quoted 44 lines

Some context on how this was noticed and reproduced below.

We started seeing flaky splats as above [2][3] on BPF CI runs after runner hardware has been upgraded. Specifically, new x86 runners are c7i.metal-24xl AWS EC2 instances, which are Intel Sapphire Rapids machines that support LA57 feature, and have it enabled.

The splats can be reproduced with qemu on any x86_64 host with
-cpu max -accel tcg

Build a kernel with:
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KASAN_INLINE=y

Boot it with kasan_multi_shot. The fault fires fast before userspace, so no rootfs is required. For example:

  qemu-system-x86_64 -display none -serial stdio -no-reboot \
    -smp 4 -m 5G -cpu max -accel tcg \
    -kernel arch/x86/boot/bzImage \
    -append "console=ttyS0,115200 earlyprintk=serial,0,115200 panic=-1 kasan_multi_shot nokaslr"

It's a timing race, so a single boot hits it only sometimes. However running several qemu instances in parallel on the same host significantly increases the hitrate.

I confirmed the proposed fix eliminates the splats.

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c?h=v7.1-rc7#n2001
[2] https://github.com/kernel-patches/bpf/actions/runs/27271262414/job/80542509369
[3] https://github.com/kernel-patches/bpf/actions/runs/27260143782/job/80505353689

---
 mm/kasan/generic.c | 7 +++++++
 1 file changed, 7 insertions(+)
mm/kasan/generic.c +0 -0
index 2b8e73f5f6a7..b5f430f2dbb6 100644

mm/kasan/generic.c line 17

Quoted 9 lines
mm/kasan/generic.c +6 -0
@@ -9,6 +9,13 @@
  *        Andrey Konovalov <[email protected]>
  */
 
+/*
+ * check_region_inline() and addr_has_metadata() can run very early.
+ * For example, in an interrupt taken while identify_cpu() has the CPU
+ * capability bits temporarily cleared.
+ */
+#define USE_EARLY_PGTABLE_L5

There are other users of addr_has_metadata(), besides ones in generic.c.

Thus, should we place this definition into mm/kasan/kasan.h? And likely guard it with ifdef KASAN_GENERIC || KASAN_SW_TAGS (HW_TAGS should not be affected by this AFAIU).

And let's also extend the comment to explain better what's going on. I like the way the issue is explained here:

https://elixir.bootlin.com/linux/v7.0.12/source/arch/x86/boot/startup/sme.c#L36

Quoted 11 lines
mm/kasan/generic.c +1 -1
+
 #include <linux/export.h>
 #include <linux/interrupt.h>
 #include <linux/init.h>
--

2.54.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/20260610175651.647515-1-ihor.solodrai%40linux.dev.

author reviewer
#4 lore raw
Quoted 57 lines · Andrey Konovalov
Quoted 56 lines · Ihor Solodrai

On x86_64 with 5-level paging (LA57) and inline generic KASAN, the following flaky splat may be observed on boot:

    BUG: KASAN: wild-memory-access in do_raw_spin_lock+0xcf/0x260
    Write of size 4 at addr ff110001000c90b8 by task swapper/0/0

    CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.1.0-rc5-gcba33e0b2907 #1 PREEMPT(full)
    Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
    Call Trace:
     <IRQ>
     dump_stack_lvl+0x54/0x70
     kasan_report+0x117/0x150
     ? do_raw_spin_lock+0xcf/0x260
     kasan_check_range+0x264/0x2c0
     do_raw_spin_lock+0xcf/0x260
     handle_edge_irq+0x35/0x770
     ? do_raw_spin_unlock+0x51/0x2a0
     __common_interrupt+0xae/0x120
     common_interrupt+0x7c/0x90
     </IRQ>
     <TASK>
     asm_common_interrupt+0x26/0x40
    RIP: 0010:identify_cpu+0x2b2/0x3460
    Code: 00 41 c7 07 00 00 00 00 4d 89 e6 49 c1 ee 03 43 0f b6 04 06 84 c0 0f 85 a3 1c 00 00 41 c7 04 24 00 00 00 00 31 c0 31 c9 0f a2 <89> c7 42 0f b6 44 05 00 84 c0 0f 85 ad 1c 00 00 41 89 3f 48 8b 44
    RSP: 0000:ffffffff97807df0 EFLAGS: 00000246
    RAX: 0000000000000020 RBX: 00000000756e6547 RCX: 000000006c65746e
    RDX: 0000000049656e69 RSI: 0000000000000000 RDI: ffffffff98632fd8
    RBP: 1ffffffff30c65fc R08: dffffc0000000000 R09: 0000000000000004
    R10: ffffffff98632fc4 R11: fffffbfff30c65fb R12: ffffffff98633050
    R13: ffffffff98633048 R14: 1ffffffff30c660a R15: ffffffff98632fe0
     identify_boot_cpu+0xd/0xd0
     arch_cpu_finalize_init+0x24/0x1f0
     start_kernel+0x31e/0x3e0
     x86_64_start_reservations+0x24/0x30
     x86_64_start_kernel+0x13a/0x140
     common_startup_64+0x12c/0x137
     </TASK>

It fires very early in boot. If kasan_multi_shot is set, the reports are non-fatal and keep repeating, and the boot CPU wedges before userspace is reached. The accessed addresses are valid 5-level kernel pointers, so the report is a false positive.

The root cause is in generic KASAN not seeing cpu_feature_enabled(X86_FEATURE_LA57) set, because the bit is cleared in identify_cpu() when the offending interrupt happens [1]:

memset(&c->x86_capability, 0, ...); /* clears X86_FEATURE_LA57 */
...
get_cpu_cap(c); /* re-reads CPUID, restores it */

addr_has_metadata() then uses the 4-level threshold, and 5-level kernel addresses fall below it, so kasan_check_range() reports them as wild-memory-access.

Quoted 6 lines
Quoted 3 lines

Define USE_EARLY_PGTABLE_L5 in mm/kasan/generic.c so addr_has_metadata() uses the stable variable, as arch/x86/mm/kasan_init_64.c already does.

It took me some digging to find which stable variable you refer to here; I think this needs to be better explained.

Hi Andrey, thank you for the quick review.

Yeah, it's:

  unsigned int __section(".data") __pgtable_l5_enabled;

I'll add more details in next revision.

Quoted 57 lines
Quoted 45 lines

Some context on how this was noticed and reproduced below.

We started seeing flaky splats as above [2][3] on BPF CI runs after runner hardware has been upgraded. Specifically, new x86 runners are c7i.metal-24xl AWS EC2 instances, which are Intel Sapphire Rapids machines that support LA57 feature, and have it enabled.

The splats can be reproduced with qemu on any x86_64 host with
-cpu max -accel tcg

Build a kernel with:
CONFIG_KASAN=y
CONFIG_KASAN_GENERIC=y
CONFIG_KASAN_INLINE=y

Boot it with kasan_multi_shot. The fault fires fast before userspace, so no rootfs is required. For example:

  qemu-system-x86_64 -display none -serial stdio -no-reboot \
    -smp 4 -m 5G -cpu max -accel tcg \
    -kernel arch/x86/boot/bzImage \
    -append "console=ttyS0,115200 earlyprintk=serial,0,115200 panic=-1 kasan_multi_shot nokaslr"

It's a timing race, so a single boot hits it only sometimes. However running several qemu instances in parallel on the same host significantly increases the hitrate.

I confirmed the proposed fix eliminates the splats.

[1] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/common.c?h=v7.1-rc7#n2001
[2] https://github.com/kernel-patches/bpf/actions/runs/27271262414/job/80542509369
[3] https://github.com/kernel-patches/bpf/actions/runs/27260143782/job/80505353689

---
 mm/kasan/generic.c | 7 +++++++
 1 file changed, 7 insertions(+)
mm/kasan/generic.c +0 -0
index 2b8e73f5f6a7..b5f430f2dbb6 100644

mm/kasan/generic.c line 17

Quoted 9 lines
mm/kasan/generic.c +6 -0
@@ -9,6 +9,13 @@
  *        Andrey Konovalov <[email protected]>
  */
 
+/*
+ * check_region_inline() and addr_has_metadata() can run very early.
+ * For example, in an interrupt taken while identify_cpu() has the CPU
+ * capability bits temporarily cleared.
+ */
+#define USE_EARLY_PGTABLE_L5

There are other users of addr_has_metadata(), besides ones in generic.c.

Quoted 3 lines

Thus, should we place this definition into mm/kasan/kasan.h? And likely guard it with ifdef KASAN_GENERIC || KASAN_SW_TAGS (HW_TAGS should not be affected by this AFAIU).

It appears setting USE_EARLY_PGTABLE_L5 in kasan.h will not work.

USE_EARLY_PGTABLE_L5 needs to be set before arch/x86/include/asm/pgtable_64_types.h is included, which is very early.

For example, it arrives to kasan/generic.c through a long path starting at linux/interrupt.h:

In file included from ./arch/x86/include/asm/pgtable_types.h:277,

                   from ./arch/x86/include/asm/processor.h:21,
                   from ./arch/x86/include/asm/cpufeature.h:5,
                   from ./arch/x86/include/asm/thread_info.h:59,
                   from ./include/linux/thread_info.h:62,
                   from ./include/linux/smp.h:119,
                   from ./include/linux/alloc_tag.h:14,
                   from ./include/linux/percpu.h:5,
                   from ./include/linux/context_tracking_state.h:5,
                   from ./include/linux/hardirq.h:5,
                   from ./include/linux/interrupt.h:11,
                   from mm/kasan/generic.c:13:

So if we want to cover all users of addr_has_metadata(), we'll have to add #define USE_EARLY_PGTABLE_L5 in multiple files: generic.c, report.c, report_generic.c, maybe others?

Any other ideas?

I determined the include path like follows:

cd linux # at 9716c086c8e8 torvalds/master
make defconfig
./scripts/config -e KASAN -e KASAN_GENERIC -e KASAN_INLINE make olddefconfig
make mm/kasan/generic.i

# then modify pgtable_64_types.h
arch/x86/include/asm/pgtable_64_types.h +1 -0
index 7eb61ef6a185..c6b754143fb4 100644
@@ -1,6 +1,7 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 #ifndef _ASM_X86_PGTABLE_64_DEFS_H
 #define _ASM_X86_PGTABLE_64_DEFS_H
+#error TRACE
 
 #include <asm/sparsemem.h>
 

And then:
eval "$(grep '^savedcmd_' mm/kasan/.generic.i.cmd | head -1 | sed 's/^savedcmd_[^ ]* := //')"

Quoted 5 lines

And let's also extend the comment to explain better what's going on. I like the way the issue is explained here:

https://elixir.bootlin.com/linux/v7.0.12/source/arch/x86/boot/startup/sme.c#L36

ok

Quoted 12 lines
Quoted 12 lines
mm/kasan/generic.c +1 -1
+
 #include <linux/export.h>
 #include <linux/interrupt.h>
 #include <linux/init.h>
--

2.54.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/20260610175651.647515-1-ihor.solodrai%40linux.dev.

#5 lore raw
In reply to Ihor Solodrai
Quoted 51 lines · Ihor Solodrai

On x86_64 with 5-level paging (LA57) and inline generic KASAN, the following flaky splat may be observed on boot:

    BUG: KASAN: wild-memory-access in do_raw_spin_lock+0xcf/0x260
    Write of size 4 at addr ff110001000c90b8 by task swapper/0/0

    CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.1.0-rc5-gcba33e0b2907 #1 PREEMPT(full)
    Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
    Call Trace:
     <IRQ>
     dump_stack_lvl+0x54/0x70
     kasan_report+0x117/0x150
     ? do_raw_spin_lock+0xcf/0x260
     kasan_check_range+0x264/0x2c0
     do_raw_spin_lock+0xcf/0x260
     handle_edge_irq+0x35/0x770
     ? do_raw_spin_unlock+0x51/0x2a0
     __common_interrupt+0xae/0x120
     common_interrupt+0x7c/0x90
     </IRQ>
     <TASK>
     asm_common_interrupt+0x26/0x40
    RIP: 0010:identify_cpu+0x2b2/0x3460
    Code: 00 41 c7 07 00 00 00 00 4d 89 e6 49 c1 ee 03 43 0f b6 04 06 84 c0 0f 85 a3 1c 00 00 41 c7 04 24 00 00 00 00 31 c0 31 c9 0f a2 <89> c7 42 0f b6 44 05 00 84 c0 0f 85 ad 1c 00 00 41 89 3f 48 8b 44
    RSP: 0000:ffffffff97807df0 EFLAGS: 00000246
    RAX: 0000000000000020 RBX: 00000000756e6547 RCX: 000000006c65746e
    RDX: 0000000049656e69 RSI: 0000000000000000 RDI: ffffffff98632fd8
    RBP: 1ffffffff30c65fc R08: dffffc0000000000 R09: 0000000000000004
    R10: ffffffff98632fc4 R11: fffffbfff30c65fb R12: ffffffff98633050
    R13: ffffffff98633048 R14: 1ffffffff30c660a R15: ffffffff98632fe0
     identify_boot_cpu+0xd/0xd0
     arch_cpu_finalize_init+0x24/0x1f0
     start_kernel+0x31e/0x3e0
     x86_64_start_reservations+0x24/0x30
     x86_64_start_kernel+0x13a/0x140
     common_startup_64+0x12c/0x137
     </TASK>

It fires very early in boot. If kasan_multi_shot is set, the reports are non-fatal and keep repeating, and the boot CPU wedges before userspace is reached. The accessed addresses are valid 5-level kernel pointers, so the report is a false positive.

The root cause is in generic KASAN not seeing cpu_feature_enabled(X86_FEATURE_LA57) set, because the bit is cleared in identify_cpu() when the offending interrupt happens [1]:

memset(&c->x86_capability, 0, ...); /* clears X86_FEATURE_LA57 */
...
get_cpu_cap(c); /* re-reads CPUID, restores it */

Quoted 8 lines

addr_has_metadata() then uses the 4-level threshold, and 5-level kernel addresses fall below it, so kasan_check_range() reports them as wild-memory-access.

Define USE_EARLY_PGTABLE_L5 in mm/kasan/generic.c so addr_has_metadata() uses the stable variable, as arch/x86/mm/kasan_init_64.c already does.

I'd rather not push USE_EARLY_PGTABLE_L5 into generic KASAN code.

It's an x86 paging detail in arch-independent files. It's incomplete (report.c and report_generic.c also call addr_has_metadata()). And it's a permanent slowdown on the KASAN hot path -- pgtable_l5_enabled() becomes a runtime load of __pgtable_l5_enabled on every check, whereas cpu_feature_enabled() gets patched to a constant after alternatives.

And it leaves the real bug in place: the window where boot_cpu_data.x86_capability reads back zero is visible to *any* cpu_feature_enabled() caller in interrupt context, not just KASAN.

The window is opened by identify_cpu() itself, so fix it there:

	diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
	--- a/arch/x86/kernel/cpu/common.c
	+++ b/arch/x86/kernel/cpu/common.c
	@@ -2003,6 +2003,7 @@ static void generic_identify(struct cpuinfo_x86 *c)
	  */
	 static void identify_cpu(struct cpuinfo_x86 *c)
	 {
	+	unsigned long flags;
	 	int i;
	@@ -2022,12 +2023,21 @@ static void identify_cpu(struct cpuinfo_x86 *c)
	 	c->x86_cache_alignment = c->x86_clflush_size;
	+
	+	/*
	+	 * x86_capability is cleared and repopulated from CPUID below. On
	+	 * the boot CPU this runs with IRQs on and before alternatives are
	+	 * patched, so cpu_feature_enabled() reads the live bits; an
	+	 * interrupt in this window sees e.g. X86_FEATURE_LA57 as disabled.
	+	 */
	+	local_irq_save(flags);
	 	memset(&c->x86_capability, 0, sizeof(c->x86_capability));
	 #ifdef CONFIG_X86_VMX_FEATURE_NAMES
	 	memset(&c->vmx_capability, 0, sizeof(c->vmx_capability));
	 #endif
	 	generic_identify(c);
	+	local_irq_restore(flags);

save/restore keeps it correct for the secondary-CPU callers that already run with IRQs off.

I reproduced your splat with parallel TCG guests (-cpu max, kasan_multi_shot):
~10% of boots hit it, 0/~200 with the above.

I am not sure how wide the irq-off window suppose to be. I scoped it to memset() .. generic_identify(), where LA57 is restored. Later code (apply_forced_caps(), ->c_init(), setup_sm*p()) only refines bits.

Widen it to be defensive, or keep it tight?

Any better solution?

Signature · 1 line
  Kiryl Shutsemau / Kirill A. Shutemov
author reviewer
#6 lore raw
Quoted 101 lines · Kiryl Shutsemau
Quoted 51 lines · Ihor Solodrai

On x86_64 with 5-level paging (LA57) and inline generic KASAN, the following flaky splat may be observed on boot:

    BUG: KASAN: wild-memory-access in do_raw_spin_lock+0xcf/0x260
    Write of size 4 at addr ff110001000c90b8 by task swapper/0/0

    CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.1.0-rc5-gcba33e0b2907 #1 PREEMPT(full)
    Hardware name: QEMU Ubuntu 24.04 PC v2 (i440FX + PIIX, arch_caps fix, 1996), BIOS 1.16.3-debian-1.16.3-2 04/01/2014
    Call Trace:
     <IRQ>
     dump_stack_lvl+0x54/0x70
     kasan_report+0x117/0x150
     ? do_raw_spin_lock+0xcf/0x260
     kasan_check_range+0x264/0x2c0
     do_raw_spin_lock+0xcf/0x260
     handle_edge_irq+0x35/0x770
     ? do_raw_spin_unlock+0x51/0x2a0
     __common_interrupt+0xae/0x120
     common_interrupt+0x7c/0x90
     </IRQ>
     <TASK>
     asm_common_interrupt+0x26/0x40
    RIP: 0010:identify_cpu+0x2b2/0x3460
    Code: 00 41 c7 07 00 00 00 00 4d 89 e6 49 c1 ee 03 43 0f b6 04 06 84 c0 0f 85 a3 1c 00 00 41 c7 04 24 00 00 00 00 31 c0 31 c9 0f a2 <89> c7 42 0f b6 44 05 00 84 c0 0f 85 ad 1c 00 00 41 89 3f 48 8b 44
    RSP: 0000:ffffffff97807df0 EFLAGS: 00000246
    RAX: 0000000000000020 RBX: 00000000756e6547 RCX: 000000006c65746e
    RDX: 0000000049656e69 RSI: 0000000000000000 RDI: ffffffff98632fd8
    RBP: 1ffffffff30c65fc R08: dffffc0000000000 R09: 0000000000000004
    R10: ffffffff98632fc4 R11: fffffbfff30c65fb R12: ffffffff98633050
    R13: ffffffff98633048 R14: 1ffffffff30c660a R15: ffffffff98632fe0
     identify_boot_cpu+0xd/0xd0
     arch_cpu_finalize_init+0x24/0x1f0
     start_kernel+0x31e/0x3e0
     x86_64_start_reservations+0x24/0x30
     x86_64_start_kernel+0x13a/0x140
     common_startup_64+0x12c/0x137
     </TASK>

It fires very early in boot. If kasan_multi_shot is set, the reports are non-fatal and keep repeating, and the boot CPU wedges before userspace is reached. The accessed addresses are valid 5-level kernel pointers, so the report is a false positive.

The root cause is in generic KASAN not seeing cpu_feature_enabled(X86_FEATURE_LA57) set, because the bit is cleared in identify_cpu() when the offending interrupt happens [1]:

memset(&c->x86_capability, 0, ...); /* clears X86_FEATURE_LA57 */
...
get_cpu_cap(c); /* re-reads CPUID, restores it */

Quoted 8 lines

addr_has_metadata() then uses the 4-level threshold, and 5-level kernel addresses fall below it, so kasan_check_range() reports them as wild-memory-access.

Define USE_EARLY_PGTABLE_L5 in mm/kasan/generic.c so addr_has_metadata() uses the stable variable, as arch/x86/mm/kasan_init_64.c already does.

I'd rather not push USE_EARLY_PGTABLE_L5 into generic KASAN code.

It's an x86 paging detail in arch-independent files. It's incomplete (report.c and report_generic.c also call addr_has_metadata()). And it's a permanent slowdown on the KASAN hot path -- pgtable_l5_enabled() becomes a runtime load of __pgtable_l5_enabled on every check, whereas cpu_feature_enabled() gets patched to a constant after alternatives.

And it leaves the real bug in place: the window where boot_cpu_data.x86_capability reads back zero is visible to *any* cpu_feature_enabled() caller in interrupt context, not just KASAN.

The window is opened by identify_cpu() itself, so fix it there:

	diff --git a/arch/x86/kernel/cpu/common.c b/arch/x86/kernel/cpu/common.c
	--- a/arch/x86/kernel/cpu/common.c
	+++ b/arch/x86/kernel/cpu/common.c
	@@ -2003,6 +2003,7 @@ static void generic_identify(struct cpuinfo_x86 *c)
	  */
	 static void identify_cpu(struct cpuinfo_x86 *c)
	 {
	+	unsigned long flags;
	 	int i;
	@@ -2022,12 +2023,21 @@ static void identify_cpu(struct cpuinfo_x86 *c)
	 	c->x86_cache_alignment = c->x86_clflush_size;
	+
	+	/*
	+	 * x86_capability is cleared and repopulated from CPUID below. On
	+	 * the boot CPU this runs with IRQs on and before alternatives are
	+	 * patched, so cpu_feature_enabled() reads the live bits; an
	+	 * interrupt in this window sees e.g. X86_FEATURE_LA57 as disabled.
	+	 */
	+	local_irq_save(flags);
	 	memset(&c->x86_capability, 0, sizeof(c->x86_capability));
	 #ifdef CONFIG_X86_VMX_FEATURE_NAMES
	 	memset(&c->vmx_capability, 0, sizeof(c->vmx_capability));
	 #endif
	 	generic_identify(c);
	+	local_irq_restore(flags);
Quoted 5 lines

save/restore keeps it correct for the secondary-CPU callers that already run with IRQs off.

I reproduced your splat with parallel TCG guests (-cpu max, kasan_multi_shot):
~10% of boots hit it, 0/~200 with the above.

Hi Kiryl, thank you for testing.

I thought of disabling IRQs here too, and AI bot flagged this as well [1].

I wasn't sure it'd be a good fix, because I don't know if it has any undesirable consequences, and whether it's actually a complete fix. For example, can a NMI hit this?

[1] https://lore.kernel.org/bpf/[email protected]/

Quoted 6 lines

I am not sure how wide the irq-off window suppose to be. I scoped it to memset() .. generic_identify(), where LA57 is restored. Later code (apply_forced_caps(), ->c_init(), setup_sm*p()) only refines bits.

Widen it to be defensive, or keep it tight?

If we scope this narrowly, then it remains LA57-specific, because we protect only a portion of the capabilities array.
IIUC to ensure x86_capability is "ready", a proper scope should be entire identify_cpu(). And this may be a problem, because mcheck_cpu_init() does kmalloc(GFP_KERNEL) [2], for example.

So I have doubts disabling IRQ is the right approach.

[2] https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git/tree/arch/x86/kernel/cpu/mce/genpool.c?h=v7.1-rc7#n119

Quoted 3 lines

Any better solution?

What if we define a static key for LA57 feature for KASAN? This will avoid the performance hit we get with USE_EARLY_PGTABLE_L5. And KASAN won't care about x86_capability being cleared. Is that feasible/acceptable?

That will be a KASAN-specific LA57-specific fix however.
The underlying "capabilities are temporarily cleared on boot" issue would remain. Although it wasn't a problem until this bug surfaced.

Something like this:

arch/x86/include/asm/kasan.h +14 -0
index d7e33c7f096b..6c1ebcb4a417 100644
@@ -36,6 +36,35 @@ static inline void kasan_populate_shadow_for_vaddr(void *va, size_t size,
                                                   int nid) { }
 #endif
 
+#ifdef CONFIG_KASAN_GENERIC
+#include <linux/jump_label.h>
+
+DECLARE_STATIC_KEY_TRUE(kasan_la57_enabled);
+
+#define addr_has_metadata addr_has_metadata
+static __always_inline bool addr_has_metadata(const void *addr)
+{
+       unsigned int shift = static_branch_likely(&kasan_la57_enabled) ? 56 : 47;
+
+       return (unsigned long)addr >= (-1UL << shift);
+}
+#endif /* CONFIG_KASAN_GENERIC */
+
 #endif
 
 #endif
arch/x86/kernel/cpu/common.c +6 -0
index a4268c47f2bc..79bad61f4891 100644
@@ -28,6 +28,7 @@
 #include <linux/stackprotector.h>
 #include <linux/utsname.h>
 #include <linux/efi.h>
+#include <linux/kasan.h>
 
 #include <asm/alternative.h>
 #include <asm/cmdline.h>
@@ -2581,6 +2582,19 @@ void __init arch_cpu_finalize_init(void)
 {
        struct cpuinfo_x86 *c = this_cpu_ptr(&cpu_info);
 
+#ifdef CONFIG_KASAN_GENERIC
+       if (!__pgtable_l5_enabled)
+               static_branch_disable(&kasan_la57_enabled);
+#endif
+
        identify_boot_cpu();
 
        select_idle_routine();
arch/x86/mm/kasan_init_64.c +3 -0
index 998b6010d6d3..3cb20b4d8799 100644
@@ -11,6 +11,7 @@
 #include <linux/sched.h>
 #include <linux/sched/task.h>
 #include <linux/vmalloc.h>
+#include <linux/jump_label.h>
 
 #include <asm/e820/types.h>
 #include <asm/pgalloc.h>
@@ -20,6 +21,12 @@
 
 extern struct range pfn_mapped[E820_MAX_ENTRIES];
 
+DEFINE_STATIC_KEY_TRUE(kasan_la57_enabled);
+
 static p4d_t tmp_p4d_table[MAX_PTRS_PER_P4D] __initdata __aligned(PAGE_SIZE);
 
 static __init void *early_alloc(size_t size, int nid, bool should_panic)
author reviewer
#7 lore raw
In reply to Ihor Solodrai
Quoted 8 lines · Ihor Solodrai

[...]

mm/kasan/generic.c +0 -0
index 2b8e73f5f6a7..b5f430f2dbb6 100644

mm/kasan/generic.c line 17

Quoted 9 lines
mm/kasan/generic.c +6 -0
@@ -9,6 +9,13 @@
  *        Andrey Konovalov <[email protected]>
  */
 
+/*
+ * check_region_inline() and addr_has_metadata() can run very early.
+ * For example, in an interrupt taken while identify_cpu() has the CPU
+ * capability bits temporarily cleared.
+ */
+#define USE_EARLY_PGTABLE_L5

Hi everyone.

Bumping the thread, as it's not clear how to proceed.

From the discussion, we've got three approached to the bugfix:

1. USE_EARLY_PGTABLE_L5 in generic KASAN

This one is probably the simplest. We add USE_EARLY_PGTABLE_L5 to the files that call addr_has_metadata(). It's KASAN / LA57 specific.

The downsides are a performance hit pointed out by Kiryl, and that it's an arch-specific switch in generic KASAN.

2. local_irq_save/restore in identify_cpu() - Kiryl's suggestion

This looks like a better fix addressing the actual problem of cleared capabilities being accessed from interrupts.

An open question for me is whether this fix is complete, and whether the scope of irq_save/restore is right. We might also be ok with it even if it's potentially incomplete.

3. Static key for LA57 in KASAN - sketched in the thread

This is essentially a bit better implementation of (1). Still KASAN / LA57 specific.

So my question to maintainers is what approach seems best? I am open to other suggestions if any.

So far this is mitigated on BPF CI with no5lvl boot param, but the bug is real, and I believe we should fix it in the kernel.

Thanks!

Quoted 4 lines
mm/kasan/generic.c +1 -0
+
 #include <linux/export.h>
 #include <linux/interrupt.h>
 #include <linux/init.h>
maintainer
#8 lore raw
Quoted 42 lines · Ihor Solodrai
Quoted 8 lines · Ihor Solodrai

[...]

mm/kasan/generic.c +0 -0
index 2b8e73f5f6a7..b5f430f2dbb6 100644

mm/kasan/generic.c line 17

Quoted 9 lines
mm/kasan/generic.c +6 -0
@@ -9,6 +9,13 @@
  *        Andrey Konovalov <[email protected]>
  */
 
+/*
+ * check_region_inline() and addr_has_metadata() can run very early.
+ * For example, in an interrupt taken while identify_cpu() has the CPU
+ * capability bits temporarily cleared.
+ */
+#define USE_EARLY_PGTABLE_L5

Hi everyone.

Bumping the thread, as it's not clear how to proceed.

From the discussion, we've got three approached to the bugfix:

1. USE_EARLY_PGTABLE_L5 in generic KASAN

This one is probably the simplest. We add USE_EARLY_PGTABLE_L5 to the files that call addr_has_metadata(). It's KASAN / LA57 specific.

The downsides are a performance hit pointed out by Kiryl, and that it's an arch-specific switch in generic KASAN.

2. local_irq_save/restore in identify_cpu() - Kiryl's suggestion

This looks like a better fix addressing the actual problem of cleared capabilities being accessed from interrupts.

An open question for me is whether this fix is complete, and whether the scope of irq_save/restore is right. We might also be ok with it even if it's potentially incomplete.

Quoted 8 lines

3. Static key for LA57 in KASAN - sketched in the thread

This is essentially a bit better implementation of (1). Still KASAN / LA57 specific.

So my question to maintainers is what approach seems best? I am open to other suggestions if any.

AFAIU, this is not only KASAN's problem, at least in theory, so #2 seems like the most appropriate solution.

If we decide to go with a KASAN-specific fix for some reason, Instead of #1 or #3, I'd suggest using kasan_disable_current()/kasan_enable_current() in identify_cpu(), the same way as as we already do for a similar problem in apply_alternatives().

Quoted 10 lines

So far this is mitigated on BPF CI with no5lvl boot param, but the bug is real, and I believe we should fix it in the kernel.

Thanks!

Quoted 4 lines
mm/kasan/generic.c +1 -0
+
 #include <linux/export.h>
 #include <linux/interrupt.h>
 #include <linux/init.h>
maintainer
#9 lore raw
In reply to Ihor Solodrai
Quoted 1 line · Ihor Solodrai

So my question to maintainers is what approach seems best?

The CPUID stuff is being rewritten currently and it should address your issue too. If not, then we need to rewrite it better.

Can you reproduce with this set applied ontop:

https://lore.kernel.org/r/[email protected]

?

Signature · 4 lines
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette
#10 lore raw
Quoted 8 lines · Borislav Petkov
Quoted 1 line · Ihor Solodrai

So my question to maintainers is what approach seems best?

The CPUID stuff is being rewritten currently and it should address your issue too. If not, then we need to rewrite it better.

Can you reproduce with this set applied ontop:

https://lore.kernel.org/r/[email protected]

Thinking about this a bit more... If Ahmed's series does fix this, I think it will be accidental. It still uses identify_cpu() and also does a memset() of the new c->cpuid structure in addition to the old c->x86_capability structure.

I'm not knocking Ahmed's series by any means. It just probably won't fix this issue.

In a perfect world early_identify_cpu() and identify_cpu() would either get consolidated into one thing. Or at least become two discrete things that initialize two completely disjoint sets of data. That way, identify_cpu() wouldn't memset() anything.

Isn't that the _real_ fix? Instead of trying to hide the inconsistency when good data is blown away, we stop blowing it away in the first place?

maintainer
#11 lore raw
Quoted 18 lines · Dave Hansen
Quoted 8 lines · Borislav Petkov
Quoted 1 line · Ihor Solodrai

So my question to maintainers is what approach seems best?

The CPUID stuff is being rewritten currently and it should address your issue too. If not, then we need to rewrite it better.

Can you reproduce with this set applied ontop:

https://lore.kernel.org/r/[email protected]

Thinking about this a bit more... If Ahmed's series does fix this, I think it will be accidental. It still uses identify_cpu() and also does a memset() of the new c->cpuid structure in addition to the old c->x86_capability structure.

I'm not knocking Ahmed's series by any means. It just probably won't fix this issue.

Quoted 7 lines

In a perfect world early_identify_cpu() and identify_cpu() would either get consolidated into one thing. Or at least become two discrete things that initialize two completely disjoint sets of data. That way, identify_cpu() wouldn't memset() anything.

Isn't that the _real_ fix? Instead of trying to hide the inconsistency when good data is blown away, we stop blowing it away in the first place?

early_ is only on the BSP and you want to scan all CPUs.

AFAIR, the last time I was looking at how we scan the CPUID leafs, we do have cases where there's blips in time when cap bits get disappeared to be rescanned again. The toggling of MSR bits which control feature flags is one thing that comes to mind.

But I'm with you on the consolidation approach. I think this is what we should do here and then that will be converted to the new way once Ahmed's lands.

Thx.

Signature · 4 lines
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette
author reviewer
#12 lore raw
Quoted 27 lines · Borislav Petkov
Quoted 18 lines · Dave Hansen
Quoted 8 lines · Borislav Petkov
Quoted 1 line · Ihor Solodrai

So my question to maintainers is what approach seems best?

The CPUID stuff is being rewritten currently and it should address your issue too. If not, then we need to rewrite it better.

Can you reproduce with this set applied ontop:

https://lore.kernel.org/r/[email protected]

Thinking about this a bit more... If Ahmed's series does fix this, I think it will be accidental. It still uses identify_cpu() and also does a memset() of the new c->cpuid structure in addition to the old c->x86_capability structure.

I'm not knocking Ahmed's series by any means. It just probably won't fix this issue.

Quoted 7 lines

In a perfect world early_identify_cpu() and identify_cpu() would either get consolidated into one thing. Or at least become two discrete things that initialize two completely disjoint sets of data. That way, identify_cpu() wouldn't memset() anything.

Isn't that the _real_ fix? Instead of trying to hide the inconsistency when good data is blown away, we stop blowing it away in the first place?

Quoted 8 lines

early_ is only on the BSP and you want to scan all CPUs.

AFAIR, the last time I was looking at how we scan the CPUID leafs, we do have cases where there's blips in time when cap bits get disappeared to be rescanned again. The toggling of MSR bits which control feature flags is one thing that comes to mind.

But I'm with you on the consolidation approach. I think this is what we should

Hello Dave, Boris. Thank you for the input.

I tried a slight refactoring of the identify_cpu() machinery to eliminate the memset(x86_capability) from the boot cpu, and it fixes the splat that I reported.

identify_cpu() is split into two parts:
* identify_cpu_scan() - the top part, up to and including the generic_identify() call
* identify_cpu() proper with the rest, no memset here

Then (gated on x86_64) identify_boot_cpu() *skips* the identify_cpu_scan() call, only executing the bottom part of current identify_cpu(). We can do that because boot cpu already did the _scan part in early_identify_cpu(), when interrupts are still disabled.

One caveat: get_model_name() is moved from generic_identify() into identify_cpu(), otherwise it wouldn't be called on boot cpu. Same for c->loops_per_jiffy = loops_per_jiffy; - it's moved to the "bottom" identify_cpu().

The behavior for secondary cpus is unchanged: identify_secondary_cpu() calls identify_cpu_scan() then immediately identify_cpu().

Please take a look at the diff below and let me know if this is a good way to proceed. I don't know exactly what I'm doing, so concerns and corrections are welcome.

arch/x86/kernel/cpu/common.c +29 -7
index a4268c47f2bc..4a655109cacf 100644
@@ -1978,8 +1978,6 @@ static void generic_identify(struct cpuinfo_x86 *c)
 
 	get_cpu_address_sizes(c);
 
-	get_model_name(c); /* Default name */
-
 	/*
 	 * ESPFIX is a strange bug.  All real CPUs have it.  Paravirt
 	 * systems that run Linux at CPL > 0 may or may not have the
@@ -1999,13 +1997,12 @@ static void generic_identify(struct cpuinfo_x86 *c)
 }
 
 /*
- * This does the hard work of actually picking apart the CPU stuff...
+ * Rebuild capability set from CPUID and re-apply the forced-cap overrides
+ * through generic_identify(). This *should* run with interrupts off, otherwise
+ * an interrupt handler might see the capability bits cleared.
  */
-static void identify_cpu(struct cpuinfo_x86 *c)
+static void identify_cpu_scan(struct cpuinfo_x86 *c)
 {
-	int i;
-
-	c->loops_per_jiffy = loops_per_jiffy;
 	c->x86_cache_size = 0;
 	c->x86_vendor = X86_VENDOR_UNKNOWN;
 	c->x86_model = c->x86_stepping = 0;	/* So far unknown... */
@@ -2028,6 +2025,19 @@ static void identify_cpu(struct cpuinfo_x86 *c)
 #endif
 
 	generic_identify(c);
+}
+
+/*
+ * This is safe to run with interrupts on.
+ * The boot CPU can run just this from arch_cpu_finalize_init(), because
+ * the scan part already happened in early_identify_cpu().
+ */
+static void identify_cpu(struct cpuinfo_x86 *c)
+{
+	int i;
+
+	c->loops_per_jiffy = loops_per_jiffy;
+	get_model_name(c); /* Default name */
 
 	cpu_parse_topology(c);
 
@@ -2154,6 +2164,17 @@ void enable_sep_cpu(void)
 
 static __init void identify_boot_cpu(void)
 {
+	/*
+	 * The boot CPU's capabilities were already scanned by early_identify_cpu().
+	 * Here identify_cpu() only finalizes them.
+	 *
+	 * 32-bit still needs the full scan here: it sets X86_BUG_ESPFIX (via
+	 * generic_identify()) and the no-CPUID cpuid_level default, which the early
+	 * path does not.
+	 */
+#ifndef CONFIG_X86_64
+	identify_cpu_scan(&boot_cpu_data);
+#endif
 	identify_cpu(&boot_cpu_data);
 	if (HAS_KERNEL_IBT && cpu_feature_enabled(X86_FEATURE_IBT))
 		pr_info("CET detected: Indirect Branch Tracking enabled\n");
@@ -2178,6 +2199,7 @@ void identify_secondary_cpu(unsigned int cpu)
 		*c = boot_cpu_data;
 	c->cpu_index = cpu;
 
+	identify_cpu_scan(c);
 	identify_cpu(c);
 #ifdef CONFIG_X86_32
 	enable_sep_cpu();
Signature · 1 line
2.53.0-Meta
maintainer
#13 lore raw
Quoted 80 lines · Ihor Solodrai
Quoted 27 lines · Borislav Petkov
Quoted 18 lines · Dave Hansen
Quoted 8 lines · Borislav Petkov
Quoted 1 line · Ihor Solodrai

So my question to maintainers is what approach seems best?

The CPUID stuff is being rewritten currently and it should address your issue too. If not, then we need to rewrite it better.

Can you reproduce with this set applied ontop:

https://lore.kernel.org/r/[email protected]

Thinking about this a bit more... If Ahmed's series does fix this, I think it will be accidental. It still uses identify_cpu() and also does a memset() of the new c->cpuid structure in addition to the old c->x86_capability structure.

I'm not knocking Ahmed's series by any means. It just probably won't fix this issue.

Quoted 7 lines

In a perfect world early_identify_cpu() and identify_cpu() would either get consolidated into one thing. Or at least become two discrete things that initialize two completely disjoint sets of data. That way, identify_cpu() wouldn't memset() anything.

Isn't that the _real_ fix? Instead of trying to hide the inconsistency when good data is blown away, we stop blowing it away in the first place?

Quoted 8 lines

early_ is only on the BSP and you want to scan all CPUs.

AFAIR, the last time I was looking at how we scan the CPUID leafs, we do have cases where there's blips in time when cap bits get disappeared to be rescanned again. The toggling of MSR bits which control feature flags is one thing that comes to mind.

But I'm with you on the consolidation approach. I think this is what we should

Hello Dave, Boris. Thank you for the input.

I tried a slight refactoring of the identify_cpu() machinery to eliminate the memset(x86_capability) from the boot cpu, and it fixes the splat that I reported.

identify_cpu() is split into two parts:
* identify_cpu_scan() - the top part, up to and including the generic_identify() call
* identify_cpu() proper with the rest, no memset here

Then (gated on x86_64) identify_boot_cpu() *skips* the identify_cpu_scan() call, only executing the bottom part of current identify_cpu(). We can do that because boot cpu already did the _scan part in early_identify_cpu(), when interrupts are still disabled.

One caveat: get_model_name() is moved from generic_identify() into identify_cpu(), otherwise it wouldn't be called on boot cpu. Same for c->loops_per_jiffy = loops_per_jiffy; - it's moved to the "bottom" identify_cpu().

The behavior for secondary cpus is unchanged: identify_secondary_cpu() calls identify_cpu_scan() then immediately identify_cpu().

Please take a look at the diff below and let me know if this is a good way to proceed. I don't know exactly what I'm doing, so concerns and corrections are welcome.

arch/x86/kernel/cpu/common.c +0 -2
index a4268c47f2bc..4a655109cacf 100644
@@ -1978,8 +1978,6 @@ static void generic_identify(struct cpuinfo_x86 *c)
 
 	get_cpu_address_sizes(c);
 
-	get_model_name(c); /* Default name */
-
 	/*
 	 * ESPFIX is a strange bug.  All real CPUs have it.  Paravirt
 	 * systems that run Linux at CPL > 0 may or may not have the

arch/x86/kernel/cpu/common.c static void generic_identify(struct cpuinfo_x86 *c) · line 2002

Quoted 7 lines
arch/x86/kernel/cpu/common.c +3 -1
@@ -1999,13 +1997,12 @@ static void generic_identify(struct cpuinfo_x86 *c)
 }
 
 /*
- * This does the hard work of actually picking apart the CPU stuff...
+ * Rebuild capability set from CPUID and re-apply the forced-cap overrides
+ * through generic_identify(). This *should* run with interrupts off, otherwise
+ * an interrupt handler might see the capability bits cleared.

And yet it doesn't run with IRQs off. So that comment doesn't need to be here. The point of the whole exercise is to not disable ugly interrupts in that path at all.

Quoted 50 lines
arch/x86/kernel/cpu/common.c +25 -4
  */
-static void identify_cpu(struct cpuinfo_x86 *c)
+static void identify_cpu_scan(struct cpuinfo_x86 *c)
 {
-	int i;
-
-	c->loops_per_jiffy = loops_per_jiffy;
 	c->x86_cache_size = 0;
 	c->x86_vendor = X86_VENDOR_UNKNOWN;
 	c->x86_model = c->x86_stepping = 0;	/* So far unknown... */
@@ -2028,6 +2025,19 @@ static void identify_cpu(struct cpuinfo_x86 *c)
 #endif
 
 	generic_identify(c);
+}
+
+/*
+ * This is safe to run with interrupts on.
+ * The boot CPU can run just this from arch_cpu_finalize_init(), because
+ * the scan part already happened in early_identify_cpu().
+ */
+static void identify_cpu(struct cpuinfo_x86 *c)
+{
+	int i;
+
+	c->loops_per_jiffy = loops_per_jiffy;
+	get_model_name(c); /* Default name */
 
 	cpu_parse_topology(c);
 
@@ -2154,6 +2164,17 @@ void enable_sep_cpu(void)
 
 static __init void identify_boot_cpu(void)
 {
+	/*
+	 * The boot CPU's capabilities were already scanned by early_identify_cpu().
+	 * Here identify_cpu() only finalizes them.
+	 *
+	 * 32-bit still needs the full scan here: it sets X86_BUG_ESPFIX (via
+	 * generic_identify()) and the no-CPUID cpuid_level default, which the early
+	 * path does not.
+	 */
+#ifndef CONFIG_X86_64
+	identify_cpu_scan(&boot_cpu_data);
+#endif
 	identify_cpu(&boot_cpu_data);
 	if (HAS_KERNEL_IBT && cpu_feature_enabled(X86_FEATURE_IBT))
 		pr_info("CET detected: Indirect Branch Tracking enabled\n");

arch/x86/kernel/cpu/common.c void identify_secondary_cpu(unsigned int cpu) · line 2205

Quoted 7 lines
arch/x86/kernel/cpu/common.c +1 -0
@@ -2178,6 +2199,7 @@ void identify_secondary_cpu(unsigned int cpu)
 		*c = boot_cpu_data;
 	c->cpu_index = cpu;
 
+	identify_cpu_scan(c);
 	identify_cpu(c);
 #ifdef CONFIG_X86_32
 	enable_sep_cpu();

Ok, thanks for that, that's a good first try. I did poke at it a bit and here's what I think should happen:

1. The part which initializes struct cpuinfo_x86 up and including the memsets should be one function called init_cpu_info() or so.

2. Then, generic_identify() should be merged into identify_cpu() basically turning the latter into a single function which does a generic CPU identification both on the BSP and on the APs.

3. #ifdef CONFIG_X86_32

        enable_sep_cpu();
#endif 
	that gunk should be stuck into a function called identify_cpu_32() and
	you'll have at the beginning of it

	if (!IS_ENABLED(CONFIG_X86_32))
		return;

and then you call that function both in identify_boot_cpu() and identify_secondary_cpu(). This way you streamline the paths and drop all that ugly ifdeffery.

4. When you do 3. above, you should be able to move this gunk

	#ifdef CONFIG_X86_32
        set_cpu_bug(c, X86_BUG_ESPFIX);
	#endif

to it too. And now everything is nicely encapsulated and straight-forward.

5. The above will allow you to have the init_cpu_info() only on 32-bit.

Oh and, looking at the ifdeffery on identify_cpu():

	#ifdef CONFIG_X86_64
	        c->x86_clflush_size = 64;
	        c->x86_phys_bits = 36;
	        c->x86_virt_bits = 48;
	#else

you could probably add a identify_cpu_64() too. Or maybe the init parts should be called separately init_cpu_info_32() and init_cpu_info_64(). You probably need to see how it looks like when you write it.

Oh, and each 1., 2., ...step above is a single patch. This'll make the review very easy too.

How does that sound? Willing to give it a try? :-)

If not, I could try to find some time and do it myself but I thought you might be willing to try it...

:-)

Thx.

Signature · 4 lines
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette
author reviewer
#14 lore raw
Quoted 192 lines · Borislav Petkov
Quoted 80 lines · Ihor Solodrai
Quoted 27 lines · Borislav Petkov
Quoted 18 lines · Dave Hansen
Quoted 8 lines · Borislav Petkov
Quoted 1 line · Ihor Solodrai

So my question to maintainers is what approach seems best?

The CPUID stuff is being rewritten currently and it should address your issue too. If not, then we need to rewrite it better.

Can you reproduce with this set applied ontop:

https://lore.kernel.org/r/[email protected]

Thinking about this a bit more... If Ahmed's series does fix this, I think it will be accidental. It still uses identify_cpu() and also does a memset() of the new c->cpuid structure in addition to the old c->x86_capability structure.

I'm not knocking Ahmed's series by any means. It just probably won't fix this issue.

Quoted 7 lines

In a perfect world early_identify_cpu() and identify_cpu() would either get consolidated into one thing. Or at least become two discrete things that initialize two completely disjoint sets of data. That way, identify_cpu() wouldn't memset() anything.

Isn't that the _real_ fix? Instead of trying to hide the inconsistency when good data is blown away, we stop blowing it away in the first place?

Quoted 8 lines

early_ is only on the BSP and you want to scan all CPUs.

AFAIR, the last time I was looking at how we scan the CPUID leafs, we do have cases where there's blips in time when cap bits get disappeared to be rescanned again. The toggling of MSR bits which control feature flags is one thing that comes to mind.

But I'm with you on the consolidation approach. I think this is what we should

Hello Dave, Boris. Thank you for the input.

I tried a slight refactoring of the identify_cpu() machinery to eliminate the memset(x86_capability) from the boot cpu, and it fixes the splat that I reported.

identify_cpu() is split into two parts:
* identify_cpu_scan() - the top part, up to and including the generic_identify() call
* identify_cpu() proper with the rest, no memset here

Then (gated on x86_64) identify_boot_cpu() *skips* the identify_cpu_scan() call, only executing the bottom part of current identify_cpu(). We can do that because boot cpu already did the _scan part in early_identify_cpu(), when interrupts are still disabled.

One caveat: get_model_name() is moved from generic_identify() into identify_cpu(), otherwise it wouldn't be called on boot cpu. Same for c->loops_per_jiffy = loops_per_jiffy; - it's moved to the "bottom" identify_cpu().

The behavior for secondary cpus is unchanged: identify_secondary_cpu() calls identify_cpu_scan() then immediately identify_cpu().

Please take a look at the diff below and let me know if this is a good way to proceed. I don't know exactly what I'm doing, so concerns and corrections are welcome.

arch/x86/kernel/cpu/common.c +0 -2
index a4268c47f2bc..4a655109cacf 100644
@@ -1978,8 +1978,6 @@ static void generic_identify(struct cpuinfo_x86 *c)
 
 	get_cpu_address_sizes(c);
 
-	get_model_name(c); /* Default name */
-
 	/*
 	 * ESPFIX is a strange bug.  All real CPUs have it.  Paravirt
 	 * systems that run Linux at CPL > 0 may or may not have the

arch/x86/kernel/cpu/common.c static void generic_identify(struct cpuinfo_x86 *c) · line 2002

Quoted 7 lines
arch/x86/kernel/cpu/common.c +3 -1
@@ -1999,13 +1997,12 @@ static void generic_identify(struct cpuinfo_x86 *c)
 }
 
 /*
- * This does the hard work of actually picking apart the CPU stuff...
+ * Rebuild capability set from CPUID and re-apply the forced-cap overrides
+ * through generic_identify(). This *should* run with interrupts off, otherwise
+ * an interrupt handler might see the capability bits cleared.

And yet it doesn't run with IRQs off. So that comment doesn't need to be here. The point of the whole exercise is to not disable ugly interrupts in that path at all.

Quoted 50 lines
arch/x86/kernel/cpu/common.c +25 -4
  */
-static void identify_cpu(struct cpuinfo_x86 *c)
+static void identify_cpu_scan(struct cpuinfo_x86 *c)
 {
-	int i;
-
-	c->loops_per_jiffy = loops_per_jiffy;
 	c->x86_cache_size = 0;
 	c->x86_vendor = X86_VENDOR_UNKNOWN;
 	c->x86_model = c->x86_stepping = 0;	/* So far unknown... */
@@ -2028,6 +2025,19 @@ static void identify_cpu(struct cpuinfo_x86 *c)
 #endif
 
 	generic_identify(c);
+}
+
+/*
+ * This is safe to run with interrupts on.
+ * The boot CPU can run just this from arch_cpu_finalize_init(), because
+ * the scan part already happened in early_identify_cpu().
+ */
+static void identify_cpu(struct cpuinfo_x86 *c)
+{
+	int i;
+
+	c->loops_per_jiffy = loops_per_jiffy;
+	get_model_name(c); /* Default name */
 
 	cpu_parse_topology(c);
 
@@ -2154,6 +2164,17 @@ void enable_sep_cpu(void)
 
 static __init void identify_boot_cpu(void)
 {
+	/*
+	 * The boot CPU's capabilities were already scanned by early_identify_cpu().
+	 * Here identify_cpu() only finalizes them.
+	 *
+	 * 32-bit still needs the full scan here: it sets X86_BUG_ESPFIX (via
+	 * generic_identify()) and the no-CPUID cpuid_level default, which the early
+	 * path does not.
+	 */
+#ifndef CONFIG_X86_64
+	identify_cpu_scan(&boot_cpu_data);
+#endif
 	identify_cpu(&boot_cpu_data);
 	if (HAS_KERNEL_IBT && cpu_feature_enabled(X86_FEATURE_IBT))
 		pr_info("CET detected: Indirect Branch Tracking enabled\n");

arch/x86/kernel/cpu/common.c void identify_secondary_cpu(unsigned int cpu) · line 2205

Quoted 7 lines
arch/x86/kernel/cpu/common.c +1 -0
@@ -2178,6 +2199,7 @@ void identify_secondary_cpu(unsigned int cpu)
 		*c = boot_cpu_data;
 	c->cpu_index = cpu;
 
+	identify_cpu_scan(c);
 	identify_cpu(c);
 #ifdef CONFIG_X86_32
 	enable_sep_cpu();

Ok, thanks for that, that's a good first try. I did poke at it a bit and here's what I think should happen:

1. The part which initializes struct cpuinfo_x86 up and including the memsets should be one function called init_cpu_info() or so.

2. Then, generic_identify() should be merged into identify_cpu() basically turning the latter into a single function which does a generic CPU identification both on the BSP and on the APs.

3. #ifdef CONFIG_X86_32

        enable_sep_cpu();
#endif 
	that gunk should be stuck into a function called identify_cpu_32() and
	you'll have at the beginning of it

	if (!IS_ENABLED(CONFIG_X86_32))
		return;

and then you call that function both in identify_boot_cpu() and identify_secondary_cpu(). This way you streamline the paths and drop all that ugly ifdeffery.

4. When you do 3. above, you should be able to move this gunk

	#ifdef CONFIG_X86_32
        set_cpu_bug(c, X86_BUG_ESPFIX);
	#endif

to it too. And now everything is nicely encapsulated and straight-forward.

5. The above will allow you to have the init_cpu_info() only on 32-bit.

Oh and, looking at the ifdeffery on identify_cpu():

	#ifdef CONFIG_X86_64
	        c->x86_clflush_size = 64;
	        c->x86_phys_bits = 36;
	        c->x86_virt_bits = 48;
	#else
Quoted 8 lines

you could probably add a identify_cpu_64() too. Or maybe the init parts should be called separately init_cpu_info_32() and init_cpu_info_64(). You probably need to see how it looks like when you write it.

Oh, and each 1., 2., ...step above is a single patch. This'll make the review very easy too.

How does that sound? Willing to give it a try? :-)

The instructions are quite detailed, thank you.

I'll try it and will send a separate series as soon as I can (likely next week).

Quoted 8 lines

If not, I could try to find some time and do it myself but I thought you might be willing to try it...

:-)

Thx.

maintainer
#15 lore raw
Quoted 1 line · Ihor Solodrai

The instructions are quite detailed, thank you.

I hope... the devil's in the detail as always so I might be talking a lot of crap too, as usual. You'll know better when you start poking at the code.

Quoted 1 line

I'll try it and will send a separate series as soon as I can (likely next week).

No worries, take your time.

And ofc, if we decide we need a fix for stable, you could make a one-liner be the first patch which maybe which disables KASAN inline when 5level page tables have been detected. Or something to that effect.

And then the last patch will remove that fix.

That is, iff there's even a need to have something in stable. I don't know how widespread kasan inline is...

Thx.

Signature · 4 lines
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette
#16 lore raw

Hi,

I hit the same false positive and can add a data point that seems relevant for the fix: it still reproduces on 7.3-rc5, i.e. after the CPUID rewrite that was expected to take care of it. The rewrite is in 7.3 -- get_cpu_cap() became cpu_detect() -- and that is exactly the function my crashes interrupt.

Two occurrences, two kernels:

7.2.0-rc5:

  BUG: KASAN: wild-memory-access in do_raw_spin_lock+0x11c/0x270
  Write of size 4 at addr ff110000158860a8 by task swapper/0/0
   <IRQ> kasan_check_range; do_raw_spin_lock; handle_edge_irq+0x2e;
         __common_interrupt; common_interrupt
   RIP: get_cpu_cap+0x12f/0xc10
        identify_cpu+0x2f9  <- arch_cpu_finalize_init+0x58 <- start_kernel+0x373

7.3.0-rc5:

  BUG: KASAN: wild-memory-access in _raw_spin_lock+0x71/0xe0
  Write of size 4 at addr ff110000010ca8a8 by task swapper/0/0
   <IRQ> kasan_check_range; _raw_spin_lock; handle_edge_irq+0x2e;
         __common_interrupt; common_interrupt
   RIP: cpu_detect+0x20a/0x470
        identify_cpu+0x2df  <- arch_cpu_finalize_init+0x25 <- start_kernel+0x39c

The 5-level hypothesis matches what I measured on the 7.2-rc5 kernel:
page_offset_base is ff11000000000000, so the faulting address is physical 0x158860a8; /proc/iomem puts that immediately past the kernel image (Kernel bss ends at 0x15577fff) and /proc/kpageflags reports SLAB for that PFN -- an ordinary early-boot slab page (most likely the irq_desc itself), and KASAN prints no "belongs to the object/cache" section for it. So I agree this is a false positive rather than a genuinely wild access.

Impact, in case it helps argue for the stable one-liner Borislav mentioned:
with panic_on_warn=1 (the syzkaller/syzbot default) this panics the booting kernel, so fuzzing VMs fail to start; I currently work around it by booting with panic_on_warn=0, which also loses real warnings.

Rate: rare and load-dependent -- 0 hits in 60 clean boots, but the fuzzer (under load) saw it about 3 times in two weeks, so it is reproducible with patience.

I filed it as <[email protected]> (both splats included) before finding this thread.

Thanks,
1sh1ro

maintainer
#17 lore raw
Quoted 4 lines · On Tue, Oct 13, 2026 at 02:06:08AM +0800, 1sh1ro wrote:

I hit the same false positive and can add a data point that seems relevant for the fix: it still reproduces on 7.3-rc5, i.e. after the CPUID rewrite that was expected to take care of it. The rewrite is in 7.3 -- get_cpu_cap() became cpu_detect() -- and that is exactly the function my crashes interrupt.

I don't know which rewrite you mean.

If you mean those patches by Ihor:

2711d67bc3a7 x86/cpu: Don't transiently clear the boot CPU's capabilities db349783ca7a x86/cpu: Move 32-bit SEP setup into identify_cpu() ad86fe2134cc x86/cpu: Inline generic_identify() into identify_cpu() 7a762b51a149 x86/cpu: Initialize boot CPU cpuinfo defaults early 54a2882ab9f7 x86/cpu: Factor init_cpu_info() out of identify_cpu()

they're in tip queued for 7.4. So not mainline yet.

You can test current tip/master if you mean them.

HTH.

Signature · 4 lines
Regards/Gruss,
    Boris.

https://people.kernel.org/tglx/notes-about-netiquette
author reviewer
#18 lore raw
In reply to Borislav Petkov
Quoted 9 lines · Borislav Petkov
Quoted 1 line · Ihor Solodrai

So my question to maintainers is what approach seems best?

The CPUID stuff is being rewritten currently and it should address your issue too. If not, then we need to rewrite it better.

Can you reproduce with this set applied ontop:

https://lore.kernel.org/r/[email protected]

Yes, the issue reproduces with this series.

Here is a splat:

[    1.554117] BUG: KASAN: wild-memory-access in do_raw_spin_lock+0x121/0x270
[    1.554117] Write of size 4 at addr ff110001000d10b8 by task swapper/0/0
[    1.554117] 
[    1.554117] CPU: 0 UID: 0 PID: 0 Comm: swapper/0 Not tainted 7.1.0-00120-g0f76bd3ff8c1 #19 PREEMPT(full) 
[    1.554117] Hardware name: QEMU Standard PC (i440FX + PIIX, 1996), BIOS 1.16.3-5.el9 11/05/2023
[    1.554117] Call Trace:
[    1.554117]  <IRQ>
[    1.554117]  dump_stack_lvl+0x68/0xa0
[    1.554117]  ? do_raw_spin_lock+0x121/0x270
[    1.554117]  kasan_report+0xca/0x100
[    1.554117]  ? do_raw_spin_lock+0x121/0x270
[    1.554117]  kasan_check_range+0x39/0x1c0
[    1.554117]  do_raw_spin_lock+0x121/0x270
[    1.554117]  ? __pfx_do_raw_spin_lock+0x10/0x10
[    1.554117]  handle_edge_irq+0x34/0x8b0
[    1.554117]  __common_interrupt+0x6e/0x170
[    1.554117]  common_interrupt+0x77/0xa0
[    1.554117]  </IRQ>
[    1.554117]  <TASK>
[    1.554117]  asm_common_interrupt+0x26/0x40
[    1.554117] RIP: 0010:__cpuid_reset_table+0xf7/0x7b0
[    1.554117] Code: 49 8d 7e 0c 45 8b 46 08 48 89 f8 48 c1 e8 03 4a 8d 2c 03 48 89 6c 24 68 42 0f b6 14 38 48 89 f8 83 e0 07 83 c0 03 38 d0 7c 08 <84> d2 0f 85 da 04 00 00 45 8b 4e 0c 4c 89 f0 48 c1 e8 03 49 01 d9
[    1.554117] RSP: 0000:ffffffff84c07d98 EFLAGS: 00010202
[    1.554117] RAX: 0000000000000007 RBX: ffffffff85a7c990 RCX: 0000000000000000
[    1.554117] RDX: 0000000000000000 RSI: fffffbfff0b4f91c RDI: ffffffff83c2dcac
[    1.554117] RBP: ffffffff85a7c990 R08: 0000000000000000 R09: 0000000000000001
[    1.554117] R10: ffffffff85a7c8ec R11: 0000000000000000 R12: 000000000000ffff
[    1.554117] R13: ffffffff85a7c990 R14: ffffffff83c2dca0 R15: dffffc0000000000
[    1.554117]  ? __pfx___cpuid_reset_table+0x10/0x10
[    1.554117]  ? __pfx___mutex_unlock_slowpath+0x10/0x10
[    1.554117]  cpuid_scan_cpu_early+0x5a/0xd0
[    1.554117]  identify_cpu+0x2c1/0x13e0
[    1.554117]  ? lock_release+0xc9/0x290
[    1.554117]  arch_cpu_finalize_init+0x25/0x3c0
[    1.554117]  start_kernel+0x304/0x3f0
[    1.554117]  x86_64_start_reservations+0x18/0x30
[    1.554117]  x86_64_start_kernel+0x116/0x130
[    1.554117]  common_startup_64+0x12c/0x138
[    1.554117]  </TASK>
Quoted 3 lines

?