[RFC PATCH 2/3] Introduce arch_prctl(ARCH_X86_CET_MARK_LEGACY_CODE)

From: Yu-cheng Yu
Date: Fri Jun 28 2019 - 15:50:32 EST


The CET legacy code bitmap covers the whole user-mode address space and is
located at the top of the user-mode address space. It is allocated only
when the first time arch_prctl(ARCH_X86_MARK_LEGACY_CODE) is called from
an application.

Introduce:

arch_prctl(ARCH_X86_MARK_LEGACY_CODE, unsigned long *buf)
Mark an address range as IBT legacy code.

*buf: starting linear address
*(buf + 1): size of the legacy code
*(buf + 2): set (1); clear (0)

Signed-off-by: Yu-cheng Yu <yu-cheng.yu@xxxxxxxxx>
---
arch/x86/include/asm/cet.h | 3 +
arch/x86/include/asm/processor.h | 13 +++-
arch/x86/include/uapi/asm/prctl.h | 1 +
arch/x86/kernel/Makefile | 2 +-
arch/x86/kernel/cet_bitmap.c | 119 ++++++++++++++++++++++++++++++
arch/x86/kernel/cet_prctl.c | 15 ++++
6 files changed, 151 insertions(+), 2 deletions(-)
create mode 100644 arch/x86/kernel/cet_bitmap.c

diff --git a/arch/x86/include/asm/cet.h b/arch/x86/include/asm/cet.h
index 9e613a6598c9..8ca497850f4a 100644
--- a/arch/x86/include/asm/cet.h
+++ b/arch/x86/include/asm/cet.h
@@ -4,6 +4,7 @@

#ifndef __ASSEMBLY__
#include <linux/types.h>
+#include <asm/processor.h>

struct task_struct;
struct sc_ext;
@@ -32,6 +33,7 @@ int cet_restore_signal(bool ia32, struct sc_ext *sc);
int cet_setup_signal(bool ia32, unsigned long rstor, struct sc_ext *sc);
int cet_setup_ibt(void);
int cet_setup_ibt_bitmap(unsigned long bitmap, unsigned long size);
+int cet_mark_legacy_code(unsigned long addr, unsigned long size, unsigned long set);
void cet_disable_ibt(void);
#else
static inline int prctl_cet(int option, unsigned long arg2) { return -EINVAL; }
@@ -44,6 +46,7 @@ static inline int cet_restore_signal(bool ia32, struct sc_ext *sc) { return -EIN
static inline int cet_setup_signal(bool ia32, unsigned long rstor,
struct sc_ext *sc) { return -EINVAL; }
static inline int cet_setup_ibt(void) { return -EINVAL; }
+static inline int cet_mark_legacy_code(unsigned long addr, unsigned long size, unsigned long set) { return -EINVAL; }
static inline void cet_disable_ibt(void) {}
#endif

diff --git a/arch/x86/include/asm/processor.h b/arch/x86/include/asm/processor.h
index 2ae7c1bf4e43..f4600157c73d 100644
--- a/arch/x86/include/asm/processor.h
+++ b/arch/x86/include/asm/processor.h
@@ -884,7 +884,18 @@ static inline void spin_lock_prefetch(const void *x)
#define TASK_SIZE_OF(child) ((test_tsk_thread_flag(child, TIF_ADDR32)) ? \
IA32_PAGE_OFFSET : TASK_SIZE_MAX)

-#define STACK_TOP TASK_SIZE_LOW
+#define MMAP_MAX (unsigned long)(test_thread_flag(TIF_ADDR32) ? \
+ TASK_SIZE : TASK_SIZE_MAX)
+
+#define IBT_BITMAP_SIZE (round_up(MMAP_MAX, PAGE_SIZE * BITS_PER_BYTE) / \
+ (PAGE_SIZE * BITS_PER_BYTE))
+
+#define IBT_BITMAP_ADDR (TASK_SIZE - IBT_BITMAP_SIZE)
+
+#define STACK_TOP (TASK_SIZE_LOW < IBT_BITMAP_ADDR - PAGE_SIZE ? \
+ TASK_SIZE_LOW : \
+ IBT_BITMAP_ADDR - PAGE_SIZE)
+
#define STACK_TOP_MAX TASK_SIZE_MAX

#define INIT_THREAD { \
diff --git a/arch/x86/include/uapi/asm/prctl.h b/arch/x86/include/uapi/asm/prctl.h
index 5eb9aeb5c662..5f670e70dc00 100644
--- a/arch/x86/include/uapi/asm/prctl.h
+++ b/arch/x86/include/uapi/asm/prctl.h
@@ -20,5 +20,6 @@
#define ARCH_X86_CET_ALLOC_SHSTK 0x3004
#define ARCH_X86_CET_GET_LEGACY_BITMAP 0x3005 /* deprecated */
#define ARCH_X86_CET_SET_LEGACY_BITMAP 0x3006
+#define ARCH_X86_CET_MARK_LEGACY_CODE 0x3007

#endif /* _ASM_X86_PRCTL_H */
diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile
index d908c95306fc..754dde1bf9ac 100644
--- a/arch/x86/kernel/Makefile
+++ b/arch/x86/kernel/Makefile
@@ -140,7 +140,7 @@ obj-$(CONFIG_UNWINDER_ORC) += unwind_orc.o
obj-$(CONFIG_UNWINDER_FRAME_POINTER) += unwind_frame.o
obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o

-obj-$(CONFIG_X86_INTEL_CET) += cet.o cet_prctl.o
+obj-$(CONFIG_X86_INTEL_CET) += cet.o cet_prctl.o cet_bitmap.o

###
# 64 bit specific files
diff --git a/arch/x86/kernel/cet_bitmap.c b/arch/x86/kernel/cet_bitmap.c
new file mode 100644
index 000000000000..6cb7ac2f66f7
--- /dev/null
+++ b/arch/x86/kernel/cet_bitmap.c
@@ -0,0 +1,119 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+
+#include <linux/mm.h>
+#include <linux/mman.h>
+#include <linux/bits.h>
+#include <asm/fpu/internal.h>
+#include <asm/cet.h>
+#include <linux/pagemap.h>
+#include <linux/err.h>
+#include <asm/vdso.h>
+
+static int alloc_bitmap(void)
+{
+ unsigned long addr;
+ u64 msr_ia32_u_cet;
+
+ addr = do_mmap_locked(NULL, IBT_BITMAP_ADDR, IBT_BITMAP_SIZE,
+ PROT_READ | PROT_WRITE,
+ MAP_ANONYMOUS | MAP_PRIVATE | MAP_FIXED_NOREPLACE,
+ VM_IBT | VM_NORESERVE, NULL);
+
+ if (IS_ERR((void *)addr))
+ return addr;
+
+ current->thread.cet.ibt_bitmap_addr = addr;
+ current->thread.cet.ibt_bitmap_size = IBT_BITMAP_SIZE;
+
+ modify_fpu_regs_begin();
+ rdmsrl(MSR_IA32_U_CET, msr_ia32_u_cet);
+ msr_ia32_u_cet |= (MSR_IA32_CET_LEG_IW_EN | addr);
+ wrmsrl(MSR_IA32_U_CET, msr_ia32_u_cet);
+ modify_fpu_regs_end();
+ return 0;
+}
+
+static int set_user_bits(unsigned long __user *buf, unsigned long buf_size,
+ unsigned long start_bit, unsigned long end_bit, unsigned long set)
+{
+ unsigned long start_ul, end_ul, total_ul;
+ int i, j, r;
+
+ if (round_up(end_bit, BITS_PER_BYTE) / BITS_PER_BYTE > buf_size)
+ end_bit = buf_size * BITS_PER_BYTE - 1;
+
+ start_ul = start_bit / BITS_PER_LONG;
+ end_ul = end_bit / BITS_PER_LONG;
+ total_ul = (end_ul - start_ul + 1);
+
+ i = start_bit % BITS_PER_LONG;
+ j = end_bit % BITS_PER_LONG;
+
+ r = 0;
+ put_user_try {
+ unsigned long tmp;
+ unsigned long x;
+
+ if (total_ul == 1) {
+ get_user_ex(tmp, &buf[start_ul]);
+
+ if (set != 0)
+ tmp |= GENMASK(j, i);
+ else
+ tmp &= ~GENMASK(j, i);
+
+ put_user_ex(tmp, &buf[start_ul]);
+ } else {
+ get_user_ex(tmp, &buf[start_ul]);
+
+ if (set != 0)
+ tmp |= GENMASK(BITS_PER_LONG - 1, i);
+ else
+ tmp &= ~GENMASK(BITS_PER_LONG - 1, i);
+
+ put_user_ex(tmp, &buf[start_ul]);
+
+ get_user_ex(tmp, &buf[end_ul]);
+
+ if (set != 0)
+ tmp |= GENMASK(j, 0);
+ else
+ tmp &= ~GENMASK(j, 0);
+
+ put_user_ex(tmp, &buf[end_ul]);
+
+ if (set != 0) {
+ for (x = start_ul + 1; x < end_ul; x++)
+ put_user_ex(~0UL, &buf[x]);
+ } else {
+ for (x = start_ul + 1; x < end_ul; x++)
+ put_user_ex(0UL, &buf[x]);
+ }
+ }
+ } put_user_catch(r);
+
+ return r;
+}
+
+int cet_mark_legacy_code(unsigned long addr, unsigned long size, unsigned long set)
+{
+ unsigned long bitmap_addr, bitmap_size;
+ int r;
+
+ if (!current->thread.cet.ibt_enabled)
+ return -EINVAL;
+
+ if (current->thread.cet.ibt_bitmap_size == 0) {
+ r = alloc_bitmap();
+ if (r)
+ return r;
+ }
+
+ bitmap_addr = current->thread.cet.ibt_bitmap_addr;
+ bitmap_size = current->thread.cet.ibt_bitmap_size;
+
+ r = set_user_bits((unsigned long * __user)bitmap_addr, bitmap_size,
+ addr / PAGE_SIZE, (addr + size - 1) / PAGE_SIZE, set);
+
+ return r;
+}
diff --git a/arch/x86/kernel/cet_prctl.c b/arch/x86/kernel/cet_prctl.c
index b7f37bbc0dd3..b2b7f462482f 100644
--- a/arch/x86/kernel/cet_prctl.c
+++ b/arch/x86/kernel/cet_prctl.c
@@ -68,6 +68,18 @@ static int handle_bitmap(unsigned long arg2)
return cet_setup_ibt_bitmap(addr, size);
}

+static int handle_mark_legacy_code(unsigned long arg2)
+{
+ unsigned long addr, size, set;
+
+ if (get_user(addr, (unsigned long __user *)arg2) ||
+ get_user(size, (unsigned long __user *)arg2 + 1) ||
+ get_user(set, (unsigned long __user *)arg2 + 2))
+ return -EFAULT;
+
+ return cet_mark_legacy_code(addr, size, set);
+}
+
int prctl_cet(int option, unsigned long arg2)
{
if (!cpu_x86_cet_enabled())
@@ -100,6 +112,9 @@ int prctl_cet(int option, unsigned long arg2)
case ARCH_X86_CET_SET_LEGACY_BITMAP:
return handle_bitmap(arg2);

+ case ARCH_X86_CET_MARK_LEGACY_CODE:
+ return handle_mark_legacy_code(arg2);
+
default:
return -EINVAL;
}
--
2.17.1