On 3/11/22 11:07, Nadav Amit wrote: > From: Nadav Amit <namit@xxxxxxxxxx> > > access_error() currently does not check for execution permission > violation. As a result, spurious page-faults due to execution permission > violation cause SIGSEGV. This is a bit muddy on the problem statement. I get that spurious faults can theoretically cause this, but *do* they in practice on current kernels? > It appears not to be an issue so far, but the next patches avoid TLB > flushes on permission promotion, which can lead to this scenario. nodejs > for instance crashes when TLB flush is avoided on permission promotion. By "it appears not to be an issue", do you mean that this suboptimal behavior can not be triggered, period? Or, it can be triggered but folks seem not to care that it can be triggered? I *think* these can be triggered today. I think it takes two threads that do something like: Thread 1 Thread 2 ======== ======== ptr = malloc(); memcpy(ptr, &code, len); exec_now = 1; while (!exec_now); call(ptr); // fault mprotect(ptr, PROT_EXEC, len); // fault sees VM_EXEC But that has a bug: exec_now is set before the mprotect(). It's not sane code. Can any sane code trigger this? > Add a check to prevent access_error() from returning mistakenly that > spurious page-faults due to instruction fetch are a reason for an access > error. > > It is assumed that error code bits of "instruction fetch" and "write" in > the hardware error code are mutual exclusive, and the change assumes so. > However, to be on the safe side, especially if hypervisors misbehave, > assert this is the case and warn otherwise. > > Cc: Andrea Arcangeli <aarcange@xxxxxxxxxx> > Cc: Andrew Cooper <andrew.cooper3@xxxxxxxxxx> > Cc: Andrew Morton <akpm@xxxxxxxxxxxxxxxxxxxx> > Cc: Andy Lutomirski <luto@xxxxxxxxxx> > Cc: Dave Hansen <dave.hansen@xxxxxxxxxxxxxxx> > Cc: Peter Xu <peterx@xxxxxxxxxx> > Cc: Peter Zijlstra <peterz@xxxxxxxxxxxxx> > Cc: Thomas Gleixner <tglx@xxxxxxxxxxxxx> > Cc: Will Deacon <will@xxxxxxxxxx> > Cc: Yu Zhao <yuzhao@xxxxxxxxxx> > Cc: Nick Piggin <npiggin@xxxxxxxxx> > Cc: x86@xxxxxxxxxx > Signed-off-by: Nadav Amit <namit@xxxxxxxxxx> > --- > arch/x86/mm/fault.c | 22 ++++++++++++++++++++-- > 1 file changed, 20 insertions(+), 2 deletions(-) > > diff --git a/arch/x86/mm/fault.c b/arch/x86/mm/fault.c > index d0074c6ed31a..ad0ef0a6087a 100644 > --- a/arch/x86/mm/fault.c > +++ b/arch/x86/mm/fault.c > @@ -1107,10 +1107,28 @@ access_error(unsigned long error_code, struct vm_area_struct *vma) > (error_code & X86_PF_INSTR), foreign)) > return 1; > > - if (error_code & X86_PF_WRITE) { > + if (error_code & (X86_PF_WRITE | X86_PF_INSTR)) { > + /* > + * CPUs are not expected to set the two error code bits > + * together, but to ensure that hypervisors do not misbehave, > + * run an additional sanity check. > + */ > + if ((error_code & (X86_PF_WRITE|X86_PF_INSTR)) == > + (X86_PF_WRITE|X86_PF_INSTR)) { > + WARN_ON_ONCE(1); > + return 1; > + } access_error() is only used on the do_user_addr_fault() side of things. Can we stick this check somewhere that also works for kernel address faults? This is a generic sanity check. It can also be in a separate patch. Also, we should *probably* stop talking about CPUs here. If there's ever something wonky with error code bits, I'd put my money on a weird hypervisor before any kind of CPU issue. > /* write, present and write, not present: */ > - if (unlikely(!(vma->vm_flags & VM_WRITE))) > + if ((error_code & X86_PF_WRITE) && > + unlikely(!(vma->vm_flags & VM_WRITE))) > + return 1; > + > + /* exec, present and exec, not present: */ > + if ((error_code & X86_PF_INSTR) && > + unlikely(!(vma->vm_flags & VM_EXEC))) > return 1; > + > return 0; > } This is getting really ugly. I think we've gone over this before, but it escapes me. Why do we need a common (X86_PF_WRITE | X86_PF_INSTR) block of code? Why can't we just add a simple X86_PF_INSN if() that mirrors the current X86_PF_WRITE one? if (error_code & X86_PF_INSN) { /* present and not exec: */ if (unlikely(!(vma->vm_flags & VM_EXEC))) return 1; return 0; }