x86/fpu/xstate: Add XFD #NM handler
authorChang S. Bae <chang.seok.bae@intel.com>
Thu, 21 Oct 2021 22:55:21 +0000 (15:55 -0700)
committerBorislav Petkov <bp@suse.de>
Tue, 26 Oct 2021 08:53:02 +0000 (10:53 +0200)
If the XFD MSR has feature bits set then #NM will be raised when user space
attempts to use an instruction related to one of these features.

When the task has no permissions to use that feature, raise SIGILL, which
is the same behavior as #UD.

If the task has permissions, calculate the new buffer size for the extended
feature set and allocate a larger fpstate. In the unlikely case that
vzalloc() fails, SIGSEGV is raised.

The allocation function will be added in the next step. Provide a stub
which fails for now.

  [ tglx: Updated serialization ]

Signed-off-by: Chang S. Bae <chang.seok.bae@intel.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Borislav Petkov <bp@suse.de>
Link: https://lore.kernel.org/r/20211021225527.10184-18-chang.seok.bae@intel.com
arch/x86/include/asm/fpu/xstate.h
arch/x86/kernel/fpu/xstate.c
arch/x86/kernel/traps.c

index cf285464eabee6a004ba560974f2c95f20b8130b..b7b145cad0197d7a68077211858064b91262438e 100644 (file)
@@ -99,6 +99,8 @@ int xfeature_size(int xfeature_nr);
 void xsaves(struct xregs_state *xsave, u64 mask);
 void xrstors(struct xregs_state *xsave, u64 mask);
 
+int xfd_enable_feature(u64 xfd_err);
+
 #ifdef CONFIG_X86_64
 DECLARE_STATIC_KEY_FALSE(__fpu_state_size_dynamic);
 #endif
index 77739b0a56d5fa7a55eef89e970d314ccf49f743..3d38558d594f3b93ab1c0559e59f2119ce7c383b 100644 (file)
@@ -1464,6 +1464,53 @@ static int xstate_request_perm(unsigned long idx)
        spin_unlock_irq(&current->sighand->siglock);
        return ret;
 }
+
+/* Place holder for now */
+static int fpstate_realloc(u64 xfeatures, unsigned int ksize,
+                          unsigned int usize)
+{
+       return -ENOMEM;
+}
+
+int xfd_enable_feature(u64 xfd_err)
+{
+       u64 xfd_event = xfd_err & XFEATURE_MASK_USER_DYNAMIC;
+       unsigned int ksize, usize;
+       struct fpu *fpu;
+
+       if (!xfd_event) {
+               pr_err_once("XFD: Invalid xfd error: %016llx\n", xfd_err);
+               return 0;
+       }
+
+       /* Protect against concurrent modifications */
+       spin_lock_irq(&current->sighand->siglock);
+
+       /* If not permitted let it die */
+       if ((xstate_get_host_group_perm() & xfd_event) != xfd_event) {
+               spin_unlock_irq(&current->sighand->siglock);
+               return -EPERM;
+       }
+
+       fpu = &current->group_leader->thread.fpu;
+       ksize = fpu->perm.__state_size;
+       usize = fpu->perm.__user_state_size;
+       /*
+        * The feature is permitted. State size is sufficient.  Dropping
+        * the lock is safe here even if more features are added from
+        * another task, the retrieved buffer sizes are valid for the
+        * currently requested feature(s).
+        */
+       spin_unlock_irq(&current->sighand->siglock);
+
+       /*
+        * Try to allocate a new fpstate. If that fails there is no way
+        * out.
+        */
+       if (fpstate_realloc(xfd_event, ksize, usize))
+               return -EFAULT;
+       return 0;
+}
 #else /* CONFIG_X86_64 */
 static inline int xstate_request_perm(unsigned long idx)
 {
index bae7582c58f564aec9b01b9900d8975bca9b623b..6ca1454a65d415ef13c6f3e634a504aa9a2d558d 100644 (file)
@@ -1108,10 +1108,48 @@ DEFINE_IDTENTRY(exc_spurious_interrupt_bug)
         */
 }
 
+static bool handle_xfd_event(struct pt_regs *regs)
+{
+       u64 xfd_err;
+       int err;
+
+       if (!IS_ENABLED(CONFIG_X86_64) || !cpu_feature_enabled(X86_FEATURE_XFD))
+               return false;
+
+       rdmsrl(MSR_IA32_XFD_ERR, xfd_err);
+       if (!xfd_err)
+               return false;
+
+       wrmsrl(MSR_IA32_XFD_ERR, 0);
+
+       /* Die if that happens in kernel space */
+       if (WARN_ON(!user_mode(regs)))
+               return false;
+
+       local_irq_enable();
+
+       err = xfd_enable_feature(xfd_err);
+
+       switch (err) {
+       case -EPERM:
+               force_sig_fault(SIGILL, ILL_ILLOPC, error_get_trap_addr(regs));
+               break;
+       case -EFAULT:
+               force_sig(SIGSEGV);
+               break;
+       }
+
+       local_irq_disable();
+       return true;
+}
+
 DEFINE_IDTENTRY(exc_device_not_available)
 {
        unsigned long cr0 = read_cr0();
 
+       if (handle_xfd_event(regs))
+               return;
+
 #ifdef CONFIG_MATH_EMULATION
        if (!boot_cpu_has(X86_FEATURE_FPU) && (cr0 & X86_CR0_EM)) {
                struct math_emu_info info = { };