Skip to contentSkip to conversation

x86: Plug racy xAPIC access of CPU hotplug code

PATCH In review Jan Kiszka 6 participants 10 messages Jan 27, 2014 – Mar 11, 2014 x86

lore
Share & tools
lore

Jan Kiszka

author
#0 lore raw

PATCH 1/1 x86: Plug racy xAPIC access of CPU hotplug code

apic_icr_write and its users in smpboot.c were apparently written under the assumption that this code would only run during early boot. But nowadays we also execute it when onlining a CPU later on while the system is fully running. That will make wakeup_cpu_via_init_nmi and, thus, also native_apic_icr_write run in plain process context. If we migrate the caller to a different CPU at the wrong time or interrupt it and write to ICR/ICR2 to send unrelated IPIs, we can end up sending INIT, SIPI or NMIs to wrong CPUs.

Fix this by disabling interrupts during the write to the ICR halves and disable preemption around waiting for ICR availability and using it.

---
 arch/x86/kernel/apic/apic.c |  4 ++++
 arch/x86/kernel/smpboot.c   | 11 +++++++++--
 2 files changed, 13 insertions(+), 2 deletions(-)
arch/x86/kernel/apic/apic.c +4 -0
index 7f26c9a..06f90b8 100644
@@ -283,8 +283,12 @@ u32 native_safe_apic_wait_icr_idle(void)
 
 void native_apic_icr_write(u32 low, u32 id)
 {
+	unsigned long flags;
+
+	local_irq_save(flags);
 	apic_write(APIC_ICR2, SET_APIC_DEST_FIELD(id));
 	apic_write(APIC_ICR, low);
+	local_irq_restore(flags);
 }
 
 u64 native_apic_icr_read(void)
arch/x86/kernel/smpboot.c +9 -2
index a32da80..37e11e5 100644
@@ -701,11 +701,15 @@ wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid,
 	int id;
 	int boot_error;
 
+	preempt_disable();
+
 	/*
 	 * Wake up AP by INIT, INIT, STARTUP sequence.
 	 */
-	if (cpu)
-		return wakeup_secondary_cpu_via_init(apicid, start_ip);
+	if (cpu) {
+		boot_error = wakeup_secondary_cpu_via_init(apicid, start_ip);
+		goto out;
+	}
 
 	/*
 	 * Wake up BSP by nmi.
@@ -725,6 +729,9 @@ wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid,
 		boot_error = wakeup_secondary_cpu_via_nmi(id, start_ip);
 	}
 
+out:
+	preempt_enable();
+
 	return boot_error;
 }
 
Signature · 1 line
1.8.1.1.298.ge7eed54
#1 lore raw
Quoted 11 lines · Jan Kiszka

apic_icr_write and its users in smpboot.c were apparently written under the assumption that this code would only run during early boot. But nowadays we also execute it when onlining a CPU later on while the system is fully running. That will make wakeup_cpu_via_init_nmi and, thus, also native_apic_icr_write run in plain process context. If we migrate the caller to a different CPU at the wrong time or interrupt it and write to ICR/ICR2 to send unrelated IPIs, we can end up sending INIT, SIPI or NMIs to wrong CPUs.

Fix this by disabling interrupts during the write to the ICR halves and disable preemption around waiting for ICR availability and using it.

If you just want to disable migration use get_cpu()/put_cpu()

-Andi

author
#2 lore raw
Quoted 10 lines · Andi Kleen
Quoted 9 lines · Jan Kiszka

apic_icr_write and its users in smpboot.c were apparently written under the assumption that this code would only run during early boot. But nowadays we also execute it when onlining a CPU later on while the system is fully running. That will make wakeup_cpu_via_init_nmi and, thus, also native_apic_icr_write run in plain process context. If we migrate the caller to a different CPU at the wrong time or interrupt it and write to ICR/ICR2 to send unrelated IPIs, we can end up sending INIT, SIPI or NMIs to wrong CPUs.

Quoted 4 lines
Quoted 2 lines

Fix this by disabling interrupts during the write to the ICR halves and disable preemption around waiting for ICR availability and using it.

If you just want to disable migration use get_cpu()/put_cpu()

Fine with me if that is now preferred. Will that be the upstream way of -rt's migrate_disable()?

Jan

Signature · 2 lines
Siemens AG, Corporate Technology, CT RTC ITP SES-DE
Corporate Competence Center Embedded Linux
#3 lore raw
Quoted 11 lines · Jan Kiszka
Quoted 10 lines · Andi Kleen
Quoted 9 lines · Jan Kiszka

apic_icr_write and its users in smpboot.c were apparently written under the assumption that this code would only run during early boot. But nowadays we also execute it when onlining a CPU later on while the system is fully running. That will make wakeup_cpu_via_init_nmi and, thus, also native_apic_icr_write run in plain process context. If we migrate the caller to a different CPU at the wrong time or interrupt it and write to ICR/ICR2 to send unrelated IPIs, we can end up sending INIT, SIPI or NMIs to wrong CPUs.

Quoted 7 lines
Quoted 4 lines
Quoted 2 lines

Fix this by disabling interrupts during the write to the ICR halves and disable preemption around waiting for ICR availability and using it.

If you just want to disable migration use get_cpu()/put_cpu()

Fine with me if that is now preferred. Will that be the upstream way of -rt's migrate_disable()?

Your original patch is fine, the suggestion to do ICR accesses with just preemption disabled is crap and is really asking for trouble: if some IRQ comes in at that point after all then it might cause all sorts of hard to debug problems (hangs, delays, missed IPIs, etc.).

Thanks,

Ingo

author
#4 lore raw
Quoted 19 lines · Ingo Molnar
Quoted 16 lines · Jan Kiszka
Quoted 14 lines · Andi Kleen
Quoted 11 lines · Jan Kiszka

apic_icr_write and its users in smpboot.c were apparently written under the assumption that this code would only run during early boot. But nowadays we also execute it when onlining a CPU later on while the system is fully running. That will make wakeup_cpu_via_init_nmi and, thus, also native_apic_icr_write run in plain process context. If we migrate the caller to a different CPU at the wrong time or interrupt it and write to ICR/ICR2 to send unrelated IPIs, we can end up sending INIT, SIPI or NMIs to wrong CPUs.

Fix this by disabling interrupts during the write to the ICR halves and disable preemption around waiting for ICR availability and using it.

If you just want to disable migration use get_cpu()/put_cpu()

Quoted 7 lines
Quoted 2 lines

Fine with me if that is now preferred. Will that be the upstream way of -rt's migrate_disable()?

Your original patch is fine, the suggestion to do ICR accesses with just preemption disabled is crap and is really asking for trouble: if some IRQ comes in at that point after all then it might cause all sorts of hard to debug problems (hangs, delays, missed IPIs, etc.).

Of course, we still need irqs off during ICR writes. I thought Andi was just suggesting to replace preempt_disable with get_cpu, maybe to document why we are disabling preemption here.

Jan

Signature · 2 lines
Siemens AG, Corporate Technology, CT RTC ITP SES-DE
Corporate Competence Center Embedded Linux
#5 lore raw
Quoted 3 lines

Of course, we still need irqs off during ICR writes. I thought Andi was just suggesting to replace preempt_disable with get_cpu, maybe to document why we are disabling preemption here.

Yes that was my point.

Also with irq-off you of course still always have races against the NMI-level machine check.

-Andi

author
#6 lore raw
Quoted 2 lines · Andi Kleen

Also with irq-off you of course still always have races against the NMI-level machine check.

The self-IPI triggered over NMI won't touch the high-part of the ICR and will properly wait for ICR to become free again. So we are safe.

Jan

Signature · 2 lines
Siemens AG, Corporate Technology, CT RTC ITP SES-DE
Corporate Competence Center Embedded Linux
#7 lore raw
In reply to Jan Kiszka
Quoted 59 lines · Jan Kiszka

apic_icr_write and its users in smpboot.c were apparently written under the assumption that this code would only run during early boot. But nowadays we also execute it when onlining a CPU later on while the system is fully running. That will make wakeup_cpu_via_init_nmi and, thus, also native_apic_icr_write run in plain process context. If we migrate the caller to a different CPU at the wrong time or interrupt it and write to ICR/ICR2 to send unrelated IPIs, we can end up sending INIT, SIPI or NMIs to wrong CPUs.

Fix this by disabling interrupts during the write to the ICR halves and disable preemption around waiting for ICR availability and using it.

---
 arch/x86/kernel/apic/apic.c |  4 ++++
 arch/x86/kernel/smpboot.c   | 11 +++++++++--
 2 files changed, 13 insertions(+), 2 deletions(-)
arch/x86/kernel/apic/apic.c +4 -0
index 7f26c9a..06f90b8 100644
@@ -283,8 +283,12 @@ u32 native_safe_apic_wait_icr_idle(void)
 
 void native_apic_icr_write(u32 low, u32 id)
 {
+	unsigned long flags;
+
+	local_irq_save(flags);
 	apic_write(APIC_ICR2, SET_APIC_DEST_FIELD(id));
 	apic_write(APIC_ICR, low);
+	local_irq_restore(flags);
 }
 
 u64 native_apic_icr_read(void)
arch/x86/kernel/smpboot.c +6 -2
index a32da80..37e11e5 100644
@@ -701,11 +701,15 @@ wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid,
 	int id;
 	int boot_error;
 
+	preempt_disable();
+
 	/*
 	 * Wake up AP by INIT, INIT, STARTUP sequence.
 	 */
-	if (cpu)
-		return wakeup_secondary_cpu_via_init(apicid, start_ip);
+	if (cpu) {
+		boot_error = wakeup_secondary_cpu_via_init(apicid, start_ip);
+		goto out;
+	}
 
 	/*
 	 * Wake up BSP by nmi.

arch/x86/kernel/smpboot.c wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid, · line 737

Quoted 9 lines
arch/x86/kernel/smpboot.c +3 -0
@@ -725,6 +729,9 @@ wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid,
 		boot_error = wakeup_secondary_cpu_via_nmi(id, start_ip);
 	}
 
+out:
+	preempt_enable();
+
 	return boot_error;
 }
 

What's the status of this? Waiting for further review, or is it queued somewhere by now? Would be good to have in 3.14, and then also in stable kernels.

Jan

#9 lore raw

[tip:x86/apic] x86/apic: Plug racy xAPIC access of CPU hotplug code

In reply to Jan Kiszka
x86/apic: Plug racy xAPIC access of CPU hotplug code
Commit-ID:  ea7bdc65bca8cf837a63e0ff7b75daed83222511
Gitweb:     http://git.kernel.org/tip/ea7bdc65bca8cf837a63e0ff7b75daed83222511
Author:     Jan Kiszka <[email protected]>
AuthorDate: Mon, 27 Jan 2014 20:14:06 +0100
Committer:  Ingo Molnar <[email protected]>
CommitDate: Tue, 11 Mar 2014 12:03:31 +0100

x86/apic: Plug racy xAPIC access of CPU hotplug code

apic_icr_write() and its users in smpboot.c were apparently written under the assumption that this code would only run during early boot. But nowadays we also execute it when onlining a CPU later on while the system is fully running. That will make wakeup_cpu_via_init_nmi and, thus, also native_apic_icr_write run in plain process context. If we migrate the caller to a different CPU at the wrong time or interrupt it and write to ICR/ICR2 to send unrelated IPIs, we can end up sending INIT, SIPI or NMIs to wrong CPUs.

Fix this by disabling interrupts during the write to the ICR halves and disable preemption around waiting for ICR availability and using it.

---
 arch/x86/kernel/apic/apic.c |  4 ++++
 arch/x86/kernel/smpboot.c   | 11 +++++++++--
 2 files changed, 13 insertions(+), 2 deletions(-)
arch/x86/kernel/apic/apic.c +4 -0
index f824d69..53e2053 100644
@@ -286,8 +286,12 @@ u32 native_safe_apic_wait_icr_idle(void)
 
 void native_apic_icr_write(u32 low, u32 id)
 {
+	unsigned long flags;
+
+	local_irq_save(flags);
 	apic_write(APIC_ICR2, SET_APIC_DEST_FIELD(id));
 	apic_write(APIC_ICR, low);
+	local_irq_restore(flags);
 }
 
 u64 native_apic_icr_read(void)
arch/x86/kernel/smpboot.c +9 -2
index c77acc6..60179ec 100644
@@ -702,11 +702,15 @@ wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid,
 	int id;
 	int boot_error;
 
+	preempt_disable();
+
 	/*
 	 * Wake up AP by INIT, INIT, STARTUP sequence.
 	 */
-	if (cpu)
-		return wakeup_secondary_cpu_via_init(apicid, start_ip);
+	if (cpu) {
+		boot_error = wakeup_secondary_cpu_via_init(apicid, start_ip);
+		goto out;
+	}
 
 	/*
 	 * Wake up BSP by nmi.
@@ -726,6 +730,9 @@ wakeup_cpu_via_init_nmi(int cpu, unsigned long start_ip, int apicid,
 		boot_error = wakeup_secondary_cpu_via_nmi(id, start_ip);
 	}
 
+out:
+	preempt_enable();
+
 	return boot_error;
 }