x86: Make x32 syscall support conditional on a kernel parameter
authorBen Hutchings <ben@decadent.org.uk>
Mon, 12 Feb 2018 23:59:26 +0000 (23:59 +0000)
committerYves-Alexis Perez <corsac@debian.org>
Wed, 21 Feb 2018 15:29:03 +0000 (15:29 +0000)
Enabling x32 in the standard amd64 kernel would increase its attack
surface while provide no benefit to the vast majority of its users.
No-one seems interested in regularly checking for vulnerabilities
specific to x32 (at least no-one with a white hat).

Still, adding another flavour just to turn on x32 seems wasteful.  And
the only differences on syscall entry are a few instructions that mask
out the x32 flag and compare the syscall number.

Use a static key to control whether x32 syscalls are really enabled, a
Kconfig parameter to set its default value and a kernel parameter
"syscall.x32" to change it at boot time.

Signed-off-by: Ben Hutchings <ben@decadent.org.uk>
Gbp-Pq: Topic features/x86
Gbp-Pq: Name x86-make-x32-syscall-support-conditional.patch

Documentation/kernel-parameters.txt
arch/x86/Kconfig
arch/x86/entry/common.c
arch/x86/entry/syscall_64.c
arch/x86/include/asm/elf.h
arch/x86/include/asm/syscall.h
arch/x86/include/asm/unistd.h

index 466c039c622b27ff5ac311526fb394131084c820..9b54031d2bb6a10ed7af537b9afd1edea7316744 100644 (file)
@@ -4070,6 +4070,10 @@ bytes respectively. Such letter suffixes can also be entirely omitted.
 
        switches=       [HW,M68k]
 
+       syscall.x32=    [KNL,x86_64] Enable/disable use of x32 syscalls on
+                       an x86_64 kernel where CONFIG_X86_X32 is enabled.
+                       Default depends on CONFIG_X86_X32_DISABLED.
+
        sysfs.deprecated=0|1 [KNL]
                        Enable/disable old style sysfs layout for old udev
                        on older distributions. When this option is enabled
index 0ca4d12ce95c67dce88386c76255f13b32bd6636..447542388dd0d5f3742c2e88a7f483ca1314052b 100644 (file)
@@ -2735,6 +2735,14 @@ config X86_X32
          elf32_x86_64 support enabled to compile a kernel with this
          option set.
 
+config X86_X32_DISABLED
+       bool "x32 ABI disabled by default"
+       depends on X86_X32
+       default n
+       help
+         Disable the x32 ABI unless explicitly enabled using the
+         kernel paramter "syscall.x32=y".
+
 config COMPAT
        def_bool y
        depends on IA32_EMULATION || X86_X32
index b0cd306dc527e77e939ddcce374135e49d350b95..6ad3d7ddbddac179310a16cf1fc6733baa270881 100644 (file)
@@ -277,8 +277,15 @@ __visible void do_syscall_64(struct pt_regs *regs)
         * table.  The only functional difference is the x32 bit in
         * regs->orig_ax, which changes the behavior of some syscalls.
         */
-       if (likely((nr & __SYSCALL_MASK) < NR_syscalls)) {
-               nr = array_index_nospec(nr & __SYSCALL_MASK, NR_syscalls);
+       if (x32_enabled) {
+               if (likely((nr & ~__X32_SYSCALL_BIT) < NR_syscalls)) {
+                       nr = array_index_nospec(nr & ~__X32_SYSCALL_BIT,
+                                               NR_syscalls);
+                       goto good;
+               }
+       } else if (likely((nr & ~0U) < NR_non_x32_syscalls)) {
+               nr = array_index_nospec(nr & ~0U, NR_non_x32_syscalls);
+       good:
                regs->ax = sys_call_table[nr](
                        regs->di, regs->si, regs->dx,
                        regs->r10, regs->r8, regs->r9);
index 6705edda4ac3e55dbc614e0f637cbd761c6c5a6d..bce23bb4b33a44cccd9505810a2a568e790af7f3 100644 (file)
@@ -3,6 +3,9 @@
 #include <linux/linkage.h>
 #include <linux/sys.h>
 #include <linux/cache.h>
+#include <linux/moduleparam.h>
+#undef MODULE_PARAM_PREFIX
+#define MODULE_PARAM_PREFIX "syscall."
 #include <asm/asm-offsets.h>
 #include <asm/syscall.h>
 
@@ -22,3 +25,50 @@ asmlinkage const sys_call_ptr_t sys_call_table[__NR_syscall_max+1] = {
        [0 ... __NR_syscall_max] = &sys_ni_syscall,
 #include <asm/syscalls_64.h>
 };
+
+#ifdef CONFIG_X86_X32_ABI
+
+/* Maybe enable x32 syscalls */
+
+#if defined(CONFIG_X86_X32_DISABLED)
+DEFINE_STATIC_KEY_FALSE(x32_enabled_skey);
+#else
+DEFINE_STATIC_KEY_TRUE(x32_enabled_skey);
+#endif
+
+static int __init x32_param_set(const char *val, const struct kernel_param *p)
+{
+       bool enabled;
+       int ret;
+
+       ret = kstrtobool(val, &enabled);
+       if (ret)
+               return ret;
+       if (IS_ENABLED(CONFIG_X86_X32_DISABLED)) {
+               if (enabled) {
+                       static_key_enable(&x32_enabled_skey.key);
+                       pr_info("Enabled x32 syscalls\n");
+               }
+       } else {
+               if (!enabled) {
+                       static_key_disable(&x32_enabled_skey.key);
+                       pr_info("Disabled x32 syscalls\n");
+               }
+       }
+       return 0;
+}
+
+static int x32_param_get(char *buffer, const struct kernel_param *p)
+{
+       return sprintf(buffer, "%c\n",
+                      static_key_enabled(&x32_enabled_skey) ? 'Y' : 'N');
+}
+
+static const struct kernel_param_ops x32_param_ops = {
+       .set = x32_param_set,
+       .get = x32_param_get,
+};
+
+arch_param_cb(x32, &x32_param_ops, NULL, 0444);
+
+#endif
index 7bcd138c3aa98dcb9f24619174a0147ddb49e9f2..3c59fb4edeb5c88203e9d64f45093e553871e2cd 100644 (file)
@@ -9,6 +9,7 @@
 #include <asm/ptrace.h>
 #include <asm/user.h>
 #include <asm/auxvec.h>
+#include <asm/syscall.h>
 
 typedef unsigned long elf_greg_t;
 
@@ -162,7 +163,8 @@ do {                                                \
 
 #define compat_elf_check_arch(x)                                       \
        (elf_check_arch_ia32(x) ||                                      \
-        (IS_ENABLED(CONFIG_X86_X32_ABI) && (x)->e_machine == EM_X86_64))
+        (IS_ENABLED(CONFIG_X86_X32_ABI) && x32_enabled &&              \
+         (x)->e_machine == EM_X86_64))
 
 #if __USER32_DS != __USER_DS
 # error "The following code assumes __USER32_DS == __USER_DS"
index 03eedc21246d5b65d39ffb45ce27c588151d7488..aede3b27645289c2cc3c66f0edc147e3e244b037 100644 (file)
@@ -16,6 +16,7 @@
 #include <uapi/linux/audit.h>
 #include <linux/sched.h>
 #include <linux/err.h>
+#include <linux/jump_label.h>
 #include <asm/asm-offsets.h>   /* For NR_syscalls */
 #include <asm/thread_info.h>   /* for TS_COMPAT */
 #include <asm/unistd.h>
@@ -35,6 +36,18 @@ extern const sys_call_ptr_t sys_call_table[];
 extern const sys_call_ptr_t ia32_sys_call_table[];
 #endif
 
+#if defined(CONFIG_X86_X32_ABI)
+#if defined(CONFIG_X86_X32_DISABLED)
+DECLARE_STATIC_KEY_FALSE(x32_enabled_skey);
+#define x32_enabled static_branch_unlikely(&x32_enabled_skey)
+#else
+DECLARE_STATIC_KEY_TRUE(x32_enabled_skey);
+#define x32_enabled static_branch_likely(&x32_enabled_skey)
+#endif
+#else
+#define x32_enabled 0
+#endif
+
 /*
  * Only the low 32 bits of orig_ax are meaningful, so we return int.
  * This importantly ignores the high bits on 64-bit, so comparisons
index 32712a925f26c63f1171b05519be5134867a2c64..9c83a35dc331bfb3200e5b83547ed4e8ba81323f 100644 (file)
@@ -5,9 +5,9 @@
 
 
 # ifdef CONFIG_X86_X32_ABI
-#  define __SYSCALL_MASK (~(__X32_SYSCALL_BIT))
+#  define NR_non_x32_syscalls 512
 # else
-#  define __SYSCALL_MASK (~0)
+#  define NR_non_x32_syscalls NR_syscalls
 # endif
 
 # ifdef CONFIG_X86_32