x86/cpu: Refactor identify_cpu()
Subsystems (3)
Ihor Solodrai
reviewercover x86/cpu: Refactor identify_cpu()
This series refactors identify_cpu() machinery on x86 and then implements a fix for a bug when cpu capabilities are temporarily cleared within an interruptable window. For more details and discussion see the relevant bug report [1].
[1] https://lore.kernel.org/bpf/[email protected]/
Ihor Solodrai (5):
x86/cpu: Factor init_cpu_info() out of identify_cpu() x86/cpu: Inline generic_identify() into identify_cpu() x86/cpu: Introduce identify_cpu_32() helper x86/cpu: Set X86_BUG_ESPFIX in identify_cpu_32() x86/cpu: Don't reset boot CPU cpuinfo in identify_cpu()
arch/x86/kernel/cpu/common.c | 114 +++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 53 deletions(-)
Signature · 1 line
2.54.0
Ihor Solodrai
reviewerPATCH 1/5 x86/cpu: Factor init_cpu_info() out of identify_cpu()
identify_cpu() begins by resetting the struct cpuinfo_x86 fields to their unknown/default values and clearing the capability arrays with memset().
Move that reset into a new helper: init_cpu_info(). This is preparation for letting the callers decide whether the reset is needed.
--- arch/x86/kernel/cpu/common.c | 17 ++++++++++------- 1 file changed, 10 insertions(+), 7 deletions(-)
arch/x86/kernel/cpu/common.c +10 -7
@@ -1993,14 +1993,8 @@ static void generic_identify(struct cpuinfo_x86 *c)#endif } -/* -* This does the hard work of actually picking apart the CPU stuff... -*/ -static void identify_cpu (struct cpuinfo_x86 *c) +static void init_cpu_info (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... */
@@ -2022,6 +2016,15 @@ static void identify_cpu(struct cpuinfo_x86 *c)#ifdef CONFIG_X86_VMX_FEATURE_NAMES memset (&c->vmx_capability,0 ,sizeof (c->vmx_capability));#endif +} + +static void identify_cpu (struct cpuinfo_x86 *c) +{ +int i; + + c->loops_per_jiffy = loops_per_jiffy; + +init_cpu_info (c);generic_identify (c);
Signature · 1 line
2.54.0
Ihor Solodrai
reviewerPATCH 2/5 x86/cpu: Inline generic_identify() into identify_cpu()
In reply to Ihor Solodraigeneric_identify() is only ever called from identify_cpu(), immediately after the cpuinfo reset. Fold it into identify_cpu() so that a single function does the job for both the boot CPU and the secondary CPUs.
--- arch/x86/kernel/cpu/common.c | 73 +++++++++++++++++------------------- 1 file changed, 34 insertions(+), 39 deletions(-)
arch/x86/kernel/cpu/common.c +34 -39
@@ -1954,16 +1954,46 @@ void check_null_seg_clears_base(struct cpuinfo_x86 *c)set_cpu_bug (c,X86_BUG_NULL_SEG ); } -static void generic_identify (struct cpuinfo_x86 *c) +static void init_cpu_info (struct cpuinfo_x86 *c) { + c->x86_cache_size =0 ; + c->x86_vendor =X86_VENDOR_UNKNOWN ; + c->x86_model = c->x86_stepping =0 ;/* So far unknown... */ + c->x86_vendor_id[0 ] ='\0' ;/* Unset */ + c->x86_model_id[0 ] ='\0' ;/* Unset */ +#ifdef CONFIG_X86_64 + c->x86_clflush_size =64 ; + c->x86_phys_bits =36 ; + c->x86_virt_bits =48 ; +#else + c->cpuid_level =-1 ;/* CPUID not detected */ + c->x86_clflush_size =32 ; + c->x86_phys_bits =32 ; + c->x86_virt_bits =32 ; +#endif + c->x86_cache_alignment = c->x86_clflush_size; +memset (&c->x86_capability,0 ,sizeof (c->x86_capability)); +memset (&c->cpuid,0 ,sizeof (c->cpuid)); +#ifdef CONFIG_X86_VMX_FEATURE_NAMES +memset (&c->vmx_capability,0 ,sizeof (c->vmx_capability)); +#endif c->extended_cpuid_level =0 ; +} + +static void identify_cpu (struct cpuinfo_x86 *c) +{ +int i; + + c->loops_per_jiffy = loops_per_jiffy; + +init_cpu_info (c);if (!cpuid_feature ())identify_cpu_without_cpuid (c); -/* cyrix could have cpuid enabled via c_identify()*/ +/* cyrix could have cpuid enabled via c_identify() */ if (!cpuid_feature ()) -return ; + goto no_cpuid;cpuid_scan_cpu (c);cpu_detect (c);
@@ -1991,43 +2021,8 @@ static void generic_identify(struct cpuinfo_x86 *c)#ifdef CONFIG_X86_32 set_cpu_bug (c,X86_BUG_ESPFIX );#endif -} - -static void init_cpu_info (struct cpuinfo_x86 *c) -{ - c->x86_cache_size =0 ; - c->x86_vendor =X86_VENDOR_UNKNOWN ; - c->x86_model = c->x86_stepping =0 ;/* So far unknown... */ - c->x86_vendor_id[0 ] ='\0' ;/* Unset */ - c->x86_model_id[0 ] ='\0' ;/* Unset */ -#ifdef CONFIG_X86_64 - c->x86_clflush_size =64 ; - c->x86_phys_bits =36 ; - c->x86_virt_bits =48 ; -#else - c->cpuid_level =-1 ;/* CPUID not detected */ - c->x86_clflush_size =32 ; - c->x86_phys_bits =32 ; - c->x86_virt_bits =32 ; -#endif - c->x86_cache_alignment = c->x86_clflush_size; -memset (&c->x86_capability,0 ,sizeof (c->x86_capability)); -memset (&c->cpuid,0 ,sizeof (c->cpuid)); -#ifdef CONFIG_X86_VMX_FEATURE_NAMES -memset (&c->vmx_capability,0 ,sizeof (c->vmx_capability)); -#endif -} - -static void identify_cpu (struct cpuinfo_x86 *c) -{ -int i; - - c->loops_per_jiffy = loops_per_jiffy; - -init_cpu_info (c); - -generic_identify (c); +no_cpuid:cpu_parse_topology (c);if (this_cpu->c_identify)
Signature · 1 line
2.54.0
Ihor Solodrai
reviewerPATCH 3/5 x86/cpu: Introduce identify_cpu_32() helper
In reply to Ihor SolodraiBoth identify_boot_cpu() and identify_secondary_cpu() call enable_sep_cpu() under a CONFIG_X86_32 #ifdef. Move that into a new identify_cpu_32() helper guarded by IS_ENABLED(CONFIG_X86_32).
--- arch/x86/kernel/cpu/common.c | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-)
arch/x86/kernel/cpu/common.c +10 -6
@@ -2146,14 +2146,20 @@ void enable_sep_cpu(void) }#endif +static void identify_cpu_32 (struct cpuinfo_x86 *c) +{ +if (!IS_ENABLED (CONFIG_X86_32 )) +return ; + +enable_sep_cpu (); +} +static __init void identify_boot_cpu (void ) {identify_cpu (&boot_cpu_data);if (HAS_KERNEL_IBT &&cpu_feature_enabled (X86_FEATURE_IBT ))pr_info ("CET detected: Indirect Branch Tracking enabled\n" ); -#ifdef CONFIG_X86_32 -enable_sep_cpu (); -#endif +identify_cpu_32 (&boot_cpu_data);cpu_detect_tlb (&boot_cpu_data);setup_cr_pinning ();
@@ -2173,9 +2179,7 @@ void identify_secondary_cpu(unsigned int cpu) c->cpu_index = cpu;identify_cpu (c); -#ifdef CONFIG_X86_32 -enable_sep_cpu (); -#endif +identify_cpu_32 (c);x86_spec_ctrl_setup_ap ();update_srbds_msr ();if (boot_cpu_has_bug (X86_BUG_GDS ))
Signature · 1 line
2.54.0
Ihor Solodrai
reviewerPATCH 4/5 x86/cpu: Set X86_BUG_ESPFIX in identify_cpu_32()
In reply to Ihor SolodraiX86_BUG_ESPFIX is 32-bit-only. Move setting it into identify_cpu_32(), next to the other 32-bit-only setup.
--- arch/x86/kernel/cpu/common.c | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-)
arch/x86/kernel/cpu/common.c +17 -17
@@ -2005,23 +2005,6 @@ static void identify_cpu(struct cpuinfo_x86 *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 -* issue, but, even if they have the issue, there's absolutely -* nothing we can do about it because we can't use the real IRET -* instruction. -* -* NB: For the time being, only 32-bit kernels support -* X86_BUG_ESPFIX as such. 64-bit kernels directly choose -* whether to apply espfix using paravirt hooks. If any -* non-paravirt system ever shows up that does *not* have the -* ESPFIX issue, we can change this. -*/ -#ifdef CONFIG_X86_32 -set_cpu_bug (c,X86_BUG_ESPFIX ); -#endif - no_cpuid:cpu_parse_topology (c);
@@ -2151,6 +2134,23 @@ static void identify_cpu_32(struct cpuinfo_x86 *c)if (!IS_ENABLED (CONFIG_X86_32 ))return ; +/* +* ESPFIX is a strange bug. All real CPUs have it. Paravirt +* systems that run Linux at CPL > 0 may or may not have the +* issue, but, even if they have the issue, there's absolutely +* nothing we can do about it because we can't use the real IRET +* instruction. +* +* NB: For the time being, only 32-bit kernels support +* X86_BUG_ESPFIX as such. 64-bit kernels directly choose +* whether to apply espfix using paravirt hooks. If any +* non-paravirt system ever shows up that does *not* have the +* ESPFIX issue, we can change this. +*/ +#ifdef CONFIG_X86_32 +set_cpu_bug (c,X86_BUG_ESPFIX ); +#endif +enable_sep_cpu (); }
Signature · 1 line
2.54.0
Ihor Solodrai
reviewerPATCH 5/5 x86/cpu: Don't reset boot CPU cpuinfo in identify_cpu()
In reply to Ihor Solodraiidentify_cpu() used to rebuild c->x86_capability from scratch:init_cpu_info() memsets the capability array to zero and identify_cpu() then re-reads CPUID. On the boot CPU identify_cpu() runs after local_irq_enable() and before alternatives are patched. An interrupt delivered in the window between the memset() and get_cpu_cap() therefore will observe cleared x86_capability [1].
However the boot CPU's capabilities have already been scanned, with interrupts off, by early_identify_cpu(). So move the init_cpu_info() call out of identify_cpu() into its callers, and skip it for the boot CPU on 64-bit.
Secondary CPUs call init_cpu_info() then identify_cpu() from identify_secondary_cpu(), with interrupts off, exactly as before. 32-bit still resets in identify_boot_cpu(): it relies on the reset for the no-CPUID cpuid_level default.
[1] https://lore.kernel.org/bpf/[email protected]/
--- arch/x86/kernel/cpu/common.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)
arch/x86/kernel/cpu/common.c +8 -2
@@ -1986,8 +1986,6 @@ static void identify_cpu(struct cpuinfo_x86 *c) c->loops_per_jiffy = loops_per_jiffy; -init_cpu_info (c); -if (!cpuid_feature ())identify_cpu_without_cpuid (c);
@@ -2156,6 +2154,13 @@ static void identify_cpu_32(struct cpuinfo_x86 *c)static __init void identify_boot_cpu (void ) { +/* +* The boot CPU's capabilities were already scanned in early_identify_cpu(). +* However 32-bit still needs to init_cpu_info() here for the no-CPUID +* cpuid_level default. +*/ +if (IS_ENABLED (CONFIG_X86_32 )) +init_cpu_info (&boot_cpu_data);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 +2183,7 @@ void identify_secondary_cpu(unsigned int cpu) *c = boot_cpu_data; c->cpu_index = cpu; +init_cpu_info (c);identify_cpu (c);identify_cpu_32 (c);x86_spec_ctrl_setup_ap ();
Signature · 1 line
2.54.0
[email protected]
AI reviewRe: [PATCH v1 5/5] x86/cpu: Don't reset boot CPU cpuinfo in identify_cpu()
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Thank you for your contribution! Sashiko AI review found 1 potential issue(s) to consider:
Pre-existing issues:
- [High] The patch deliberately leaves 32-bit systems vulnerable to the exact same interrupt window race condition that it fixes for 64-bit systems. --
commit 7225f9ebac0ba182d1da83ea9f37319d7bdecfdb
Author: Ihor Solodrai <[email protected]>
x86/cpu: Don't reset boot CPU cpuinfo in identify_cpu()
This patch moves the init_cpu_info() call out of identify_cpu() into its callers and skips it for the boot CPU on 64-bit systems. This prevents a window where interrupts can observe cleared CPU capabilities. 32-bit systems still reset capabilities in identify_boot_cpu() to maintain the no-CPUID default.
Quoted 4 lines
arch/x86/kernel/cpu/common.c +0
-0
[ ... ]
arch/x86/kernel/cpu/common.c static void identify_cpu_32(struct cpuinfo_x86 *c) · line 2163
Quoted 11 lines
arch/x86/kernel/cpu/common.c +7
-0@@ -2156,6 +2154,13 @@ static void identify_cpu_32(struct cpuinfo_x86 *c)static __init void identify_boot_cpu (void ) { +/* +* The boot CPU's capabilities were already scanned in early_identify_cpu(). +* However 32-bit still needs to init_cpu_info() here for the no-CPUID +* cpuid_level default. +*/ +if (IS_ENABLED (CONFIG_X86_32 )) +init_cpu_info (&boot_cpu_data);
[Severity: High]
This is a pre-existing issue, but does leaving the call to init_cpu_info() here leave 32-bit systems vulnerable to the exact same interrupt window race condition that this patch fixes for 64-bit?
Because start_kernel() calls local_irq_enable() before arch_cpu_finalize_init() calls identify_boot_cpu(), an interrupt can fire immediately after init_cpu_info() clears the capabilities:
arch/x86/kernel/cpu/common.c:init_cpu_info() {
...
memset(&c->x86_capability, 0, sizeof(c->x86_capability));
...
}If a timer tick or other hardware interrupt fires right after this memset, and the interrupt handler dynamically evaluates boot_cpu_has(), won't it falsely observe missing capabilities and potentially cause a crash or incorrect behavior during boot?
Signature · 1 line
Sashiko AI review · https://sashiko.dev/#/patchset/[email protected]?part=5
Borislav Petkov
maintainerRe: [PATCH v1 5/5] x86/cpu: Don't reset boot CPU cpuinfo in identify_cpu()
In reply to Ihor SolodraiQuoted 12 lines · Ihor Solodrai
identify_cpu()used to rebuildc->x86_capabilityfrom scratch:init_cpu_info()memsets the capability array to zero andidentify_cpu()then re-reads CPUID. On the boot CPUidentify_cpu()runs afterlocal_irq_enable()and before alternatives are patched. An interrupt delivered in the window between thememset()andget_cpu_cap()therefore will observe clearedx86_capability[1].However the boot CPU's capabilities have already been scanned, with interrupts off, by
early_identify_cpu(). So move theinit_cpu_info()call out ofidentify_cpu()into its callers, and skip it for the boot CPU on 64-bit.
Quoted 4 lines
Secondary CPUs call
init_cpu_info()thenidentify_cpu()fromidentify_secondary_cpu(), with interrupts off, exactly as before. 32-bit still resets inidentify_boot_cpu(): it relies on the reset for the no-CPUIDcpuid_leveldefault.
Ok, this commit message is all over the place. Please structure it something like this:
1. Prepare the context for the explanation briefly.
2. Explain the problem at hand.
3. "It happens because of <...>"
4. "Fix it by doing X"
5. "(Potentially do Y)."
And some of those above are optional depending on the issue being explained.
Also, do not talk about what your patches do - that should (hopefully) be visible from the diff itself. Rather, talk about *why* you're doing what you're doing.
In the patches where you do only mechanical movement, add
"No functional changes."
to the commit message to denote that.
Quoted 35 lines
[1] https://lore.kernel.org/bpf/[email protected]/
Signed-off-by Ihor Solodrai <[email protected]>--- arch/x86/kernel/cpu/common.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)arch/x86/kernel/cpu/common.c +7
-2@@ -1986,8 +1986,6 @@ static void identify_cpu(struct cpuinfo_x86 *c) c->loops_per_jiffy = loops_per_jiffy; -init_cpu_info (c); -if (!cpuid_feature ())identify_cpu_without_cpuid (c);@@ -2156,6 +2154,13 @@ static void identify_cpu_32(struct cpuinfo_x86 *c)static __init void identify_boot_cpu (void ) { +/* +* The boot CPU's capabilities were already scanned in early_identify_cpu(). +* However 32-bit still needs to init_cpu_info() here for the no-CPUID +* cpuid_level default. +*/ +if (IS_ENABLED (CONFIG_X86_32 )) +init_cpu_info (&boot_cpu_data);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 2188
Quoted 6 lines
arch/x86/kernel/cpu/common.c +1
-0@@ -2178,6 +2183,7 @@ void identify_secondary_cpu(unsigned int cpu) *c = boot_cpu_data; c->cpu_index = cpu; +init_cpu_info (c);identify_cpu (c);identify_cpu_32 (c);
Also, when you look at the final version, do you see how identify_cpu() and identify_cpu_32() are soo close together so that you can basically move the body of identify_cpu_32() to the end of identify_cpu() and then you don't need that helper either.
Other than that, the flows do look better. 32-bit still needs dealing with the no-CPUID case but that's for another day. I can't really find it in me to care, actually, 32-bit needs to go away and we'll simplify a looot of code. Oh well.
Thx.
Signature · 4 lines
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquetteIhor Solodrai
reviewerRe: [PATCH v1 5/5] x86/cpu: Don't reset boot CPU cpuinfo in identify_cpu()
Quoted 38 lines · Borislav Petkov
Quoted 12 lines · Ihor Solodrai
identify_cpu()used to rebuildc->x86_capabilityfrom scratch:init_cpu_info()memsets the capability array to zero andidentify_cpu()then re-reads CPUID. On the boot CPUidentify_cpu()runs afterlocal_irq_enable()and before alternatives are patched. An interrupt delivered in the window between thememset()andget_cpu_cap()therefore will observe clearedx86_capability[1].However the boot CPU's capabilities have already been scanned, with interrupts off, by
early_identify_cpu(). So move theinit_cpu_info()call out ofidentify_cpu()into its callers, and skip it for the boot CPU on 64-bit.Quoted 4 lines
Secondary CPUs call
init_cpu_info()thenidentify_cpu()fromidentify_secondary_cpu(), with interrupts off, exactly as before. 32-bit still resets inidentify_boot_cpu(): it relies on the reset for the no-CPUIDcpuid_leveldefault.Ok, this commit message is all over the place. Please structure it something like this:
1. Prepare the context for the explanation briefly.
2. Explain the problem at hand.
3. "It happens because of <...>"
4. "Fix it by doing X"
5. "(Potentially do Y)."
And some of those above are optional depending on the issue being explained.
Also, do not talk about what your patches do - that should (hopefully) be visible from the diff itself. Rather, talk about *why* you're doing what you're doing.
Quoted 5 lines
In the patches where you do only mechanical movement, add
"No functional changes."
to the commit message to denote that.
Thank you for the review and the feedback. I'll respin.
Quoted 40 lines
Quoted 40 lines
[1] https://lore.kernel.org/bpf/[email protected]/
Signed-off-by Ihor Solodrai <[email protected]>--- arch/x86/kernel/cpu/common.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-)arch/x86/kernel/cpu/common.c +7
-2@@ -1986,8 +1986,6 @@ static void identify_cpu(struct cpuinfo_x86 *c) c->loops_per_jiffy = loops_per_jiffy; -init_cpu_info (c); -if (!cpuid_feature ())identify_cpu_without_cpuid (c);@@ -2156,6 +2154,13 @@ static void identify_cpu_32(struct cpuinfo_x86 *c)static __init void identify_boot_cpu (void ) { +/* +* The boot CPU's capabilities were already scanned in early_identify_cpu(). +* However 32-bit still needs to init_cpu_info() here for the no-CPUID +* cpuid_level default. +*/ +if (IS_ENABLED (CONFIG_X86_32 )) +init_cpu_info (&boot_cpu_data);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 +2183,7 @@ void identify_secondary_cpu(unsigned int cpu) *c = boot_cpu_data; c->cpu_index = cpu;
Quoted 8 lines
arch/x86/kernel/cpu/common.c void identify_secondary_cpu(unsigned int cpu) · line 2188
Quoted 3 lines
arch/x86/kernel/cpu/common.c +1
-0+init_cpu_info (c);identify_cpu (c);identify_cpu_32 (c);Also, when you look at the final version, do you see how
identify_cpu()andidentify_cpu_32()are soo close together so that you can basically move the body ofidentify_cpu_32()to the end ofidentify_cpu()and then you don't need that helper either.
I actually did notice that, but I guess I fixated on the idea that 32bit part should be encapsulated. I'll fold identify_cpu_32() into identify_cpu() in the next revision.
Thanks!
Quoted 8 lines
Other than that, the flows do look better. 32-bit still needs dealing with the no-CPUID case but that's for another day. I can't really find it in me to care, actually, 32-bit needs to go away and we'll simplify a looot of code. Oh well.
Thx.
Ihor Solodrai
reviewerQuoted 8 lines · Ihor Solodrai
This series refactors
identify_cpu()machinery on x86 and then implements a fix for a bug when cpu capabilities are temporarily cleared within an interruptable window. For more details and discussion see the relevant bug report [1].Suggested-by Borislav Petkov <[email protected]>Signed-off-by Ihor Solodrai <[email protected]>
Quoted 8 lines
[1] https://lore.kernel.org/bpf/[email protected]/
Ihor Solodrai (5):
x86/cpu: Factorinit_cpu_info()out ofidentify_cpu()x86/cpu: Inlinegeneric_identify()intoidentify_cpu()x86/cpu: Introduceidentify_cpu_32()helper x86/cpu: SetX86_BUG_ESPFIXinidentify_cpu_32()x86/cpu: Don't reset boot CPU cpuinfo inidentify_cpu()
Hi Boris, everyone.
Bumping the thread as it's been a couple of weeks.
Please take a look when you get a chance. Thanks!
Quoted 4 lines
arch/x86/kernel/cpu/common.c | 114 +++++++++++++++++++---------------- 1 file changed, 61 insertions(+), 53 deletions(-)
Borislav Petkov
maintainerQuoted 1 line · Ihor Solodrai
Bumping the thread as it's been a couple of weeks.
You don't have to - it is on the TODO list.
Quoted 1 line
Please take a look when you get a chance. Thanks!
There's this thing called fires which need to be handled yesterday and we're all drowning in them. So yeah, I know, but pls be patient.
Thx.
Signature · 4 lines
Regards/Gruss,
Boris.
https://people.kernel.org/tglx/notes-about-netiquette