amd64: Only update fsbase/gsbase in pcb for curthread.

Before the pcb is copied to the new thread during cpu_fork() and
cpu_copy_thread(), the kernel re-reads the current register values in
case they are stale.  This is done by setting PCB_FULL_IRET in
pcb_flags.

This works fine for user threads, but the creation of kernel processes
and kernel threads do not follow the normal synchronization rules for
pcb_flags.  Specifically, new kernel processes are always forked from
thread0, not from curthread, so adjusting pcb_flags via a simple
instruction without the LOCK prefix can race with thread0 running on
another CPU.  Similarly, kthread_add() clones from the first thread in
the relevant kernel process, not from curthread.  In practice, Netflix
encountered a panic where the pcb_flags in the first kthread of the
KTLS process were trashed due to update_pcb_bases() in
cpu_copy_thread() running from thread0 to create one of the other KTLS
threads racing with the first KTLS kthread calling fpu_kern_thread()
on another CPU.  In the panicking case, the write to update pcb_flags
in fpu_kern_thread() was lost triggering an "Unregistered use of FPU
in kernel" panic when the first KTLS kthread later tried to use the
FPU.

Reported by:	gallatin
Discussed with:	kib
MFC after:	1 week
Sponsored by:	Netflix
Differential Revision:	https://reviews.freebsd.org/D29023
This commit is contained in:
John Baldwin 2021-03-12 09:45:18 -08:00
parent 65f4ff4e68
commit 9221145868

View file

@ -166,7 +166,8 @@ cpu_fork(struct thread *td1, struct proc *p2, struct thread *td2, int flags)
/* Ensure that td1's pcb is up to date. */
fpuexit(td1);
update_pcb_bases(td1->td_pcb);
if (td1 == curthread)
update_pcb_bases(td1->td_pcb);
/* Point the stack and pcb to the actual location */
set_top_of_stack_td(td2);
@ -568,7 +569,8 @@ cpu_copy_thread(struct thread *td, struct thread *td0)
* Those not loaded individually below get their default
* values here.
*/
update_pcb_bases(td0->td_pcb);
if (td0 == curthread)
update_pcb_bases(td0->td_pcb);
bcopy(td0->td_pcb, pcb2, sizeof(*pcb2));
clear_pcb_flags(pcb2, PCB_FPUINITDONE | PCB_USERFPUINITDONE |
PCB_KERNFPU);