|
@@ -10,8 +10,12 @@
|
|
|
#include <linux/syscalls.h>
|
|
|
#include <linux/sched/sysctl.h>
|
|
|
|
|
|
+#include <asm/i387.h>
|
|
|
+#include <asm/insn.h>
|
|
|
#include <asm/mman.h>
|
|
|
#include <asm/mpx.h>
|
|
|
+#include <asm/processor.h>
|
|
|
+#include <asm/fpu-internal.h>
|
|
|
|
|
|
static const char *mpx_mapping_name(struct vm_area_struct *vma)
|
|
|
{
|
|
@@ -266,10 +270,11 @@ bad_opcode:
|
|
|
siginfo_t *mpx_generate_siginfo(struct pt_regs *regs,
|
|
|
struct xsave_struct *xsave_buf)
|
|
|
{
|
|
|
+ struct bndreg *bndregs, *bndreg;
|
|
|
+ siginfo_t *info = NULL;
|
|
|
struct insn insn;
|
|
|
uint8_t bndregno;
|
|
|
int err;
|
|
|
- siginfo_t *info;
|
|
|
|
|
|
err = mpx_insn_decode(&insn, regs);
|
|
|
if (err)
|
|
@@ -285,6 +290,15 @@ siginfo_t *mpx_generate_siginfo(struct pt_regs *regs,
|
|
|
err = -EINVAL;
|
|
|
goto err_out;
|
|
|
}
|
|
|
+ /* get the bndregs _area_ of the xsave structure */
|
|
|
+ bndregs = get_xsave_addr(xsave_buf, XSTATE_BNDREGS);
|
|
|
+ if (!bndregs) {
|
|
|
+ err = -EINVAL;
|
|
|
+ goto err_out;
|
|
|
+ }
|
|
|
+ /* now go select the individual register in the set of 4 */
|
|
|
+ bndreg = &bndregs[bndregno];
|
|
|
+
|
|
|
info = kzalloc(sizeof(*info), GFP_KERNEL);
|
|
|
if (!info) {
|
|
|
err = -ENOMEM;
|
|
@@ -300,10 +314,8 @@ siginfo_t *mpx_generate_siginfo(struct pt_regs *regs,
|
|
|
* complains when casting from integers to different-size
|
|
|
* pointers.
|
|
|
*/
|
|
|
- info->si_lower = (void __user *)(unsigned long)
|
|
|
- (xsave_buf->bndreg[bndregno].lower_bound);
|
|
|
- info->si_upper = (void __user *)(unsigned long)
|
|
|
- (~xsave_buf->bndreg[bndregno].upper_bound);
|
|
|
+ info->si_lower = (void __user *)(unsigned long)bndreg->lower_bound;
|
|
|
+ info->si_upper = (void __user *)(unsigned long)~bndreg->upper_bound;
|
|
|
info->si_addr_lsb = 0;
|
|
|
info->si_signo = SIGSEGV;
|
|
|
info->si_errno = 0;
|
|
@@ -319,5 +331,206 @@ siginfo_t *mpx_generate_siginfo(struct pt_regs *regs,
|
|
|
}
|
|
|
return info;
|
|
|
err_out:
|
|
|
+ /* info might be NULL, but kfree() handles that */
|
|
|
+ kfree(info);
|
|
|
return ERR_PTR(err);
|
|
|
}
|
|
|
+
|
|
|
+static __user void *task_get_bounds_dir(struct task_struct *tsk)
|
|
|
+{
|
|
|
+ struct bndcsr *bndcsr;
|
|
|
+
|
|
|
+ if (!cpu_feature_enabled(X86_FEATURE_MPX))
|
|
|
+ return MPX_INVALID_BOUNDS_DIR;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The bounds directory pointer is stored in a register
|
|
|
+ * only accessible if we first do an xsave.
|
|
|
+ */
|
|
|
+ fpu_save_init(&tsk->thread.fpu);
|
|
|
+ bndcsr = get_xsave_addr(&tsk->thread.fpu.state->xsave, XSTATE_BNDCSR);
|
|
|
+ if (!bndcsr)
|
|
|
+ return MPX_INVALID_BOUNDS_DIR;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Make sure the register looks valid by checking the
|
|
|
+ * enable bit.
|
|
|
+ */
|
|
|
+ if (!(bndcsr->bndcfgu & MPX_BNDCFG_ENABLE_FLAG))
|
|
|
+ return MPX_INVALID_BOUNDS_DIR;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Lastly, mask off the low bits used for configuration
|
|
|
+ * flags, and return the address of the bounds table.
|
|
|
+ */
|
|
|
+ return (void __user *)(unsigned long)
|
|
|
+ (bndcsr->bndcfgu & MPX_BNDCFG_ADDR_MASK);
|
|
|
+}
|
|
|
+
|
|
|
+int mpx_enable_management(struct task_struct *tsk)
|
|
|
+{
|
|
|
+ void __user *bd_base = MPX_INVALID_BOUNDS_DIR;
|
|
|
+ struct mm_struct *mm = tsk->mm;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * runtime in the userspace will be responsible for allocation of
|
|
|
+ * the bounds directory. Then, it will save the base of the bounds
|
|
|
+ * directory into XSAVE/XRSTOR Save Area and enable MPX through
|
|
|
+ * XRSTOR instruction.
|
|
|
+ *
|
|
|
+ * fpu_xsave() is expected to be very expensive. Storing the bounds
|
|
|
+ * directory here means that we do not have to do xsave in the unmap
|
|
|
+ * path; we can just use mm->bd_addr instead.
|
|
|
+ */
|
|
|
+ bd_base = task_get_bounds_dir(tsk);
|
|
|
+ down_write(&mm->mmap_sem);
|
|
|
+ mm->bd_addr = bd_base;
|
|
|
+ if (mm->bd_addr == MPX_INVALID_BOUNDS_DIR)
|
|
|
+ ret = -ENXIO;
|
|
|
+
|
|
|
+ up_write(&mm->mmap_sem);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+int mpx_disable_management(struct task_struct *tsk)
|
|
|
+{
|
|
|
+ struct mm_struct *mm = current->mm;
|
|
|
+
|
|
|
+ if (!cpu_feature_enabled(X86_FEATURE_MPX))
|
|
|
+ return -ENXIO;
|
|
|
+
|
|
|
+ down_write(&mm->mmap_sem);
|
|
|
+ mm->bd_addr = MPX_INVALID_BOUNDS_DIR;
|
|
|
+ up_write(&mm->mmap_sem);
|
|
|
+ return 0;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * With 32-bit mode, MPX_BT_SIZE_BYTES is 4MB, and the size of each
|
|
|
+ * bounds table is 16KB. With 64-bit mode, MPX_BT_SIZE_BYTES is 2GB,
|
|
|
+ * and the size of each bounds table is 4MB.
|
|
|
+ */
|
|
|
+static int allocate_bt(long __user *bd_entry)
|
|
|
+{
|
|
|
+ unsigned long expected_old_val = 0;
|
|
|
+ unsigned long actual_old_val = 0;
|
|
|
+ unsigned long bt_addr;
|
|
|
+ int ret = 0;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Carve the virtual space out of userspace for the new
|
|
|
+ * bounds table:
|
|
|
+ */
|
|
|
+ bt_addr = mpx_mmap(MPX_BT_SIZE_BYTES);
|
|
|
+ if (IS_ERR((void *)bt_addr))
|
|
|
+ return PTR_ERR((void *)bt_addr);
|
|
|
+ /*
|
|
|
+ * Set the valid flag (kinda like _PAGE_PRESENT in a pte)
|
|
|
+ */
|
|
|
+ bt_addr = bt_addr | MPX_BD_ENTRY_VALID_FLAG;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * Go poke the address of the new bounds table in to the
|
|
|
+ * bounds directory entry out in userspace memory. Note:
|
|
|
+ * we may race with another CPU instantiating the same table.
|
|
|
+ * In that case the cmpxchg will see an unexpected
|
|
|
+ * 'actual_old_val'.
|
|
|
+ *
|
|
|
+ * This can fault, but that's OK because we do not hold
|
|
|
+ * mmap_sem at this point, unlike some of the other part
|
|
|
+ * of the MPX code that have to pagefault_disable().
|
|
|
+ */
|
|
|
+ ret = user_atomic_cmpxchg_inatomic(&actual_old_val, bd_entry,
|
|
|
+ expected_old_val, bt_addr);
|
|
|
+ if (ret)
|
|
|
+ goto out_unmap;
|
|
|
+
|
|
|
+ /*
|
|
|
+ * The user_atomic_cmpxchg_inatomic() will only return nonzero
|
|
|
+ * for faults, *not* if the cmpxchg itself fails. Now we must
|
|
|
+ * verify that the cmpxchg itself completed successfully.
|
|
|
+ */
|
|
|
+ /*
|
|
|
+ * We expected an empty 'expected_old_val', but instead found
|
|
|
+ * an apparently valid entry. Assume we raced with another
|
|
|
+ * thread to instantiate this table and desclare succecss.
|
|
|
+ */
|
|
|
+ if (actual_old_val & MPX_BD_ENTRY_VALID_FLAG) {
|
|
|
+ ret = 0;
|
|
|
+ goto out_unmap;
|
|
|
+ }
|
|
|
+ /*
|
|
|
+ * We found a non-empty bd_entry but it did not have the
|
|
|
+ * VALID_FLAG set. Return an error which will result in
|
|
|
+ * a SEGV since this probably means that somebody scribbled
|
|
|
+ * some invalid data in to a bounds table.
|
|
|
+ */
|
|
|
+ if (expected_old_val != actual_old_val) {
|
|
|
+ ret = -EINVAL;
|
|
|
+ goto out_unmap;
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+out_unmap:
|
|
|
+ vm_munmap(bt_addr & MPX_BT_ADDR_MASK, MPX_BT_SIZE_BYTES);
|
|
|
+ return ret;
|
|
|
+}
|
|
|
+
|
|
|
+/*
|
|
|
+ * When a BNDSTX instruction attempts to save bounds to a bounds
|
|
|
+ * table, it will first attempt to look up the table in the
|
|
|
+ * first-level bounds directory. If it does not find a table in
|
|
|
+ * the directory, a #BR is generated and we get here in order to
|
|
|
+ * allocate a new table.
|
|
|
+ *
|
|
|
+ * With 32-bit mode, the size of BD is 4MB, and the size of each
|
|
|
+ * bound table is 16KB. With 64-bit mode, the size of BD is 2GB,
|
|
|
+ * and the size of each bound table is 4MB.
|
|
|
+ */
|
|
|
+static int do_mpx_bt_fault(struct xsave_struct *xsave_buf)
|
|
|
+{
|
|
|
+ unsigned long bd_entry, bd_base;
|
|
|
+ struct bndcsr *bndcsr;
|
|
|
+
|
|
|
+ bndcsr = get_xsave_addr(xsave_buf, XSTATE_BNDCSR);
|
|
|
+ if (!bndcsr)
|
|
|
+ return -EINVAL;
|
|
|
+ /*
|
|
|
+ * Mask off the preserve and enable bits
|
|
|
+ */
|
|
|
+ bd_base = bndcsr->bndcfgu & MPX_BNDCFG_ADDR_MASK;
|
|
|
+ /*
|
|
|
+ * The hardware provides the address of the missing or invalid
|
|
|
+ * entry via BNDSTATUS, so we don't have to go look it up.
|
|
|
+ */
|
|
|
+ bd_entry = bndcsr->bndstatus & MPX_BNDSTA_ADDR_MASK;
|
|
|
+ /*
|
|
|
+ * Make sure the directory entry is within where we think
|
|
|
+ * the directory is.
|
|
|
+ */
|
|
|
+ if ((bd_entry < bd_base) ||
|
|
|
+ (bd_entry >= bd_base + MPX_BD_SIZE_BYTES))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ return allocate_bt((long __user *)bd_entry);
|
|
|
+}
|
|
|
+
|
|
|
+int mpx_handle_bd_fault(struct xsave_struct *xsave_buf)
|
|
|
+{
|
|
|
+ /*
|
|
|
+ * Userspace never asked us to manage the bounds tables,
|
|
|
+ * so refuse to help.
|
|
|
+ */
|
|
|
+ if (!kernel_managing_mpx_tables(current->mm))
|
|
|
+ return -EINVAL;
|
|
|
+
|
|
|
+ if (do_mpx_bt_fault(xsave_buf)) {
|
|
|
+ force_sig(SIGSEGV, current);
|
|
|
+ /*
|
|
|
+ * The force_sig() is essentially "handling" this
|
|
|
+ * exception, so we do not pass up the error
|
|
|
+ * from do_mpx_bt_fault().
|
|
|
+ */
|
|
|
+ }
|
|
|
+ return 0;
|
|
|
+}
|