arch/intel64: don't touch the outgoing stack after releasing the csection

up_switch_context() and up_exit() released the critical section and then
kept using the outgoing task's stack: a call/ret through
nxsched_switch_context() and the call into x86_64_fullcontextrestore().
Once the lock is released the outgoing task can be woken and run by
another CPU on that same stack, so those accesses race with it.

Release the critical section as the last step and enter
x86_64_fullcontextrestore() with a jmp so nothing is read from or
written to the outgoing stack after the release.

Assisted-by: Claude Code
Signed-off-by: raiden00pl <raiden00@railab.me>
This commit is contained in:
raiden00pl 2026-09-01 13:27:39 +02:00 • committed by Alan C. Assis
parent a5bf9ad9c9
commit dcca9a4735
2 changed files with 18 additions and 17 deletions

View file

@ -87,23 +87,19 @@ void up_exit(int status)
x86_64_restore_auxstate(tcb);
/* Restore the cpu lock */
restore_critical_section(tcb, this_cpu());
#ifdef CONFIG_ARCH_KERNEL_STACK
/* Update kernel stack top pointer */
x86_64_set_ktopstk(tcb->xcp.ktopstk);
#endif
/* Then switch contexts */
x86_64_fullcontextrestore(tcb->xcp.regs);
/* x86_64_fullcontextrestore() should not return but could if the software
* interrupts are disabled.
/* Restore the cpu lock. This must come last and the final jump must
* not touch the stack (see up_switch_context()).
*/
PANIC();
restore_critical_section(tcb, this_cpu());
__asm__ volatile ("jmp x86_64_fullcontextrestore"
:: "D" (tcb->xcp.regs) : "memory");
__builtin_unreachable();
}

View file

@ -86,6 +86,7 @@ void up_switch_context(struct tcb_s *tcb, struct tcb_s *rtcb)
else if (!up_saveusercontext(rtcb->xcp.regs))
{
struct tcb_s **running_task;
cpu = this_cpu();
x86_64_restore_auxstate(tcb);
@ -101,10 +102,6 @@ void up_switch_context(struct tcb_s *tcb, struct tcb_s *rtcb)
tcb = this_task();
#endif
/* Restore the cpu lock */
restore_critical_section(tcb, cpu);
/* Update scheduler parameters */
running_task = &g_running_tasks[cpu];
@ -117,8 +114,16 @@ void up_switch_context(struct tcb_s *tcb, struct tcb_s *rtcb)
*running_task = tcb;
/* Then switch contexts */
/* Restore the cpu lock. This makes the outgoing task wakeable by
* other CPUs while this CPU still runs on the outgoing task's
* stack, so it must come last and the final jump must not touch
* the stack (a call would push the return address onto it).
*/
x86_64_fullcontextrestore(tcb->xcp.regs);
restore_critical_section(tcb, cpu);
__asm__ volatile ("jmp x86_64_fullcontextrestore"
:: "D" (tcb->xcp.regs) : "memory");
__builtin_unreachable();
}
}