On Tue Nov 21, 2023 at 2:10 PM AEST, Timothy Pearson wrote: > ----- Original Message ----- > > From: "Michael Ellerman" <m...@ellerman.id.au> > > To: "Timothy Pearson" <tpear...@raptorengineering.com> > > Cc: "Jens Axboe" <ax...@kernel.dk>, "regressions" > > <regressi...@lists.linux.dev>, "npiggin" <npig...@gmail.com>, > > "christophe leroy" <christophe.le...@csgroup.eu>, "linuxppc-dev" > > <linuxppc-dev@lists.ozlabs.org> > > Sent: Monday, November 20, 2023 5:39:52 PM > > Subject: Re: [PATCH v2] powerpc: Don't clobber fr0/vs0 during fp|altivec > > register save > > > Timothy Pearson <tpear...@raptorengineering.com> writes: > >> ----- Original Message ----- > >>> From: "Michael Ellerman" <m...@ellerman.id.au> > > ... > >>> > >>> But we now have a new path, because io-uring can call copy_process() via > >>> create_io_thread() from the signal handling path. That's OK if the signal > >>> is > >>> handled as we return from a syscall, but it's not OK if the signal is > >>> handled > >>> due to some other interrupt. > >>> > >>> Which is: > >>> > >>> interrupt_return_srr_user() > >>> interrupt_exit_user_prepare() > >>> interrupt_exit_user_prepare_main() > >>> do_notify_resume() > >>> get_signal() > >>> task_work_run() > >>> create_worker_cb() > >>> create_io_worker() > >>> copy_process() > >>> dup_task_struct() > >>> arch_dup_task_struct() > >>> flush_all_to_thread() > >>> save_all() > >>> if (tsk->thread.regs->msr & MSR_FP) > >>> save_fpu() > >>> # fr0 is clobbered and potentially live in > >>> userspace > >>> > >>> > >>> So tldr I think the corruption is only an issue since io-uring started > >>> doing > >>> the clone via signal, which I think matches the observed timeline of this > >>> bug > >>> appearing. > >> > >> I agree the corruption really only started showing up in earnest on > >> io_uring clone-via-signal, as this was confirmed several times in the > >> course of debugging. > > > > Thanks. > > > >> Note as well that I may very well have a wrong call order in the > >> commit message, since I was relying on a couple of WARN_ON() macros I > >> inserted to check for a similar (but not identical) condition and > >> didn't spend much time getting new traces after identifying the root > >> cause. > > > > Yep no worries. I'll reword it to incorporate the full path from my mail. > > > >> I went back and grabbed some real world system-wide stack traces, since I > >> now > >> know what to trigger on. A typical example is: > >> > >> interrupt_return_srr_user() > >> interrupt_exit_user_prepare() > >> interrupt_exit_user_prepare_main() > >> schedule() > >> __schedule() > >> __switch_to() > >> giveup_all() > >> # tsk->thread.regs->msr MSR_FP is still set here > >> __giveup_fpu() > >> save_fpu() > >> # fr0 is clobbered and potentially live in userspace > > > > fr0 is not live there. > <snip> > > ie. it clears the FP etc. bits from the task's MSR. That means the FP > > state will be reloaded from the thread struct before the task is run again. > > So a little more detail on this, just to put it to rest properly vs. assuming > hand analysis caught every possible pathway. :) > > The debugging that generates this stack trace also verifies the following in > __giveup_fpu(): > > 1.) tsk->thread.fp_state.fpr doesn't contain the FPSCR contents prior to > calling save_fpu() > 2.) tsk->thread.fp_state.fpr contains the FPSCR contents directly after > calling save_fpu() > 3.) MSR_FP is set both in the task struct and in the live MSR. > > Only if all three conditions are met will it generate the trace. This is a > generalization of the hack I used to find the problem in the first place. > > If the state will subsequently be reloaded from the thread struct, that means > we're reloading the registers from the thread struct that we just verified > was corrupted by the earlier save_fpu() call. There are only two ways I can > see for that to be true -- one is if the registers were already clobbered > when giveup_all() was entered, and the other is if save_fpu() went ahead and > clobbered them right here inside giveup_all(). > > To see which scenario we were dealing with, I added a bit more > instrumentation to dump the current register state if MSR_FP bit was already > set in registers (i.e. not dumping data from task struct, but using the live > FPU registers instead), and sure enough the registers are corrupt on entry, > so something else has already called save_fpu() before we even hit > giveup_all() in this call chain. > > Unless I'm missing something, doesn't this effectively mean that anything > interrupting a task can hit this bug? Or, put another way, I'm seeing > several processes hit this exact call chain with the corrupt register going > back out to userspace without io_uring even in the mix, so there seems to be > another pathway in play. These traces are from a qemu guest, in case it > matters given the kvm path is possibly susceptible. > > Just a few things to think about. The FPU patch itself definitely resolves > the problems; I used a sledgehammer approach *specifically* so that there is > no place for a rare call sequence we didn't consider to hit it again down the > line. :)
I don't think interrupts are supposed to use (or save) FP/VEC registers. So you're allowed to _take_ interrupts while FP/VEC are being saved or used, just not be preempted, block, or return to user. Hence all the preempt_disable() around these things. Not that we cover all these with warnings very well in the enable_kernel_* functions AFAIKS. We could add more checks. At least interrupts enabled would be good. balance and user exit checks should somewhat be covered by preempt count implicitly. Thanks, Nick