| From a5652bff891e2feb6f84baca3723b168a37d65b1 Mon Sep 17 00:00:00 2001 |
| From: Guenter Roeck <groeck@chromium.org> |
| Date: Fri, 21 Sep 2018 12:42:54 -0700 |
| Subject: [PATCH] CHROMIUM: x86: Add alt-syscall support |
| |
| BUG=chromium:885330 |
| TEST=Boot with alt-syscall enabled |
| |
| Change-Id: I4a6fd929d46f0cd1ae2aca748e8befb87affabac |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| |
| Conflicts (x86 32-bit API changes): |
| arch/x86/Kconfig |
| arch/x86/entry/common.c |
| |
| [rebase54(groeck): Address conflicts; squashed: |
| CHROMIUM: Restrict ALT_SYSCALL code to 64 bit builds |
| CHROMIUM: alt-syscall: Remove entries for x86_32 |
| ] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| [rebase510(groeck): Major conflicts; took patch from continuous rebase] |
| Signed-off-by: Guenter Roeck <groeck@chromium.org> |
| Change-Id: Ic8548898ae64d8bda70712be3668a2873e59f8e1 |
| --- |
| arch/x86/Kconfig | 1 + |
| arch/x86/entry/common.c | 22 +++++++++- |
| arch/x86/include/asm/syscall.h | 1 + |
| arch/x86/include/asm/thread_info.h | 35 +++++++++++++++ |
| arch/x86/kernel/Makefile | 3 ++ |
| arch/x86/kernel/alt-syscall.c | 70 ++++++++++++++++++++++++++++++ |
| security/chromiumos/Kconfig | 1 - |
| 7 files changed, 131 insertions(+), 2 deletions(-) |
| create mode 100644 arch/x86/kernel/alt-syscall.c |
| |
| diff --git a/arch/x86/Kconfig b/arch/x86/Kconfig |
| index 49270655e827..4d9fd6c0b39d 100644 |
| --- a/arch/x86/Kconfig |
| +++ b/arch/x86/Kconfig |
| @@ -267,6 +267,7 @@ config X86 |
| select X86_FEATURE_NAMES if PROC_FS |
| select PROC_PID_ARCH_STATUS if PROC_FS |
| imply IMA_SECURE_AND_OR_TRUSTED_BOOT if EFI |
| + select ARCH_HAS_ALT_SYSCALL if X86_64 |
| |
| config INSTRUCTION_DECODER |
| def_bool y |
| diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c |
| index 6c2826417b33..fd60091303ac 100644 |
| --- a/arch/x86/entry/common.c |
| +++ b/arch/x86/entry/common.c |
| @@ -44,13 +44,26 @@ static __always_inline bool do_syscall_x64(struct pt_regs *regs, int nr) |
| * numbers for comparisons. |
| */ |
| unsigned int unr = nr; |
| +#ifdef CONFIG_ALT_SYSCALL |
| + struct thread_info *ti; |
| +#endif |
| |
| +#ifdef CONFIG_ALT_SYSCALL |
| + ti = current_thread_info(); |
| + if (likely(unr < ti->nr_syscalls)) { |
| + unr = array_index_nospec(unr, ti->nr_syscalls); |
| + regs->ax = ti->sys_call_table[unr](regs); |
| + return true; |
| + } |
| + return false; |
| +#else |
| if (likely(unr < NR_syscalls)) { |
| unr = array_index_nospec(unr, NR_syscalls); |
| regs->ax = sys_call_table[unr](regs); |
| return true; |
| } |
| return false; |
| +#endif |
| } |
| |
| static __always_inline bool do_syscall_x32(struct pt_regs *regs, int nr) |
| @@ -106,13 +119,20 @@ static __always_inline void do_syscall_32_irqs_on(struct pt_regs *regs, int nr) |
| * numbers for comparisons. |
| */ |
| unsigned int unr = nr; |
| - |
| +#ifdef CONFIG_ALT_SYSCALL |
| + struct thread_info *ti = current_thread_info(); |
| + if (likely(unr < ti->ia32_nr_syscalls)) { |
| + unr = array_index_nospec(unr, ti->ia32_nr_syscalls); |
| + regs->ax = ti->ia32_sys_call_table[unr](regs); |
| + } |
| +#else |
| if (likely(unr < IA32_NR_syscalls)) { |
| unr = array_index_nospec(unr, IA32_NR_syscalls); |
| regs->ax = ia32_sys_call_table[unr](regs); |
| } else if (nr != -1) { |
| regs->ax = __ia32_sys_ni_syscall(regs); |
| } |
| +#endif |
| } |
| |
| /* Handles int $0x80 */ |
| diff --git a/arch/x86/include/asm/syscall.h b/arch/x86/include/asm/syscall.h |
| index f7e2d82d24fb..d44258d66254 100644 |
| --- a/arch/x86/include/asm/syscall.h |
| +++ b/arch/x86/include/asm/syscall.h |
| @@ -21,6 +21,7 @@ extern const sys_call_ptr_t sys_call_table[]; |
| |
| #if defined(CONFIG_X86_32) |
| #define ia32_sys_call_table sys_call_table |
| +#define ia32_nr_syscalls nr_syscalls |
| #else |
| /* |
| * These may not exist, but still put the prototypes in so we |
| diff --git a/arch/x86/include/asm/thread_info.h b/arch/x86/include/asm/thread_info.h |
| index de406d93b515..b20f111c83cf 100644 |
| --- a/arch/x86/include/asm/thread_info.h |
| +++ b/arch/x86/include/asm/thread_info.h |
| @@ -50,6 +50,10 @@ |
| */ |
| #ifndef __ASSEMBLY__ |
| struct task_struct; |
| + |
| +/* same as sys_call_ptr_t from asm/syscall.h */ |
| +typedef asmlinkage long (*ti_sys_call_ptr_t)(const struct pt_regs *); |
| + |
| #include <asm/cpufeature.h> |
| #include <linux/atomic.h> |
| |
| @@ -57,11 +61,42 @@ struct thread_info { |
| unsigned long flags; /* low level flags */ |
| unsigned long syscall_work; /* SYSCALL_WORK_ flags */ |
| u32 status; /* thread synchronous flags */ |
| +#ifdef CONFIG_ALT_SYSCALL |
| + /* |
| + * This uses nr_syscalls instead of nr_syscall_max because we want |
| + * to be able to entirely disable a syscall table (e.g. compat) by |
| + * setting nr_syscalls to 0. This requires some careful work in |
| + * the syscall entry assembly code, most variations use ..._max. |
| + */ |
| + unsigned int nr_syscalls; /* size of below */ |
| + const ti_sys_call_ptr_t *sys_call_table; |
| +# ifdef CONFIG_IA32_EMULATION |
| + unsigned int ia32_nr_syscalls; /* size of below */ |
| + const ti_sys_call_ptr_t *ia32_sys_call_table; |
| +# endif |
| +#endif |
| }; |
| |
| +#ifdef CONFIG_ALT_SYSCALL |
| +# ifdef CONFIG_IA32_EMULATION |
| +# define INIT_THREAD_INFO_SYSCALL_COMPAT \ |
| + .ia32_nr_syscalls = IA32_NR_syscalls, \ |
| + .ia32_sys_call_table = ia32_sys_call_table, |
| +# else |
| +# define INIT_THREAD_INFO_SYSCALL_COMPAT /* */ |
| +# endif |
| +# define INIT_THREAD_INFO_SYSCALL \ |
| + .nr_syscalls = NR_syscalls, \ |
| + .sys_call_table = sys_call_table, \ |
| + INIT_THREAD_INFO_SYSCALL_COMPAT |
| +#else |
| +# define INIT_THREAD_INFO_SYSCALL /* */ |
| +#endif |
| + |
| #define INIT_THREAD_INFO(tsk) \ |
| { \ |
| .flags = 0, \ |
| + INIT_THREAD_INFO_SYSCALL \ |
| } |
| |
| #else /* !__ASSEMBLY__ */ |
| diff --git a/arch/x86/kernel/Makefile b/arch/x86/kernel/Makefile |
| index 3e625c61f008..ca815f55a73b 100644 |
| --- a/arch/x86/kernel/Makefile |
| +++ b/arch/x86/kernel/Makefile |
| @@ -150,6 +150,9 @@ obj-$(CONFIG_UNWINDER_FRAME_POINTER) += unwind_frame.o |
| obj-$(CONFIG_UNWINDER_GUESS) += unwind_guess.o |
| |
| obj-$(CONFIG_AMD_MEM_ENCRYPT) += sev.o |
| + |
| +obj-$(CONFIG_ALT_SYSCALL) += alt-syscall.o |
| + |
| ### |
| # 64 bit specific files |
| ifeq ($(CONFIG_X86_64),y) |
| diff --git a/arch/x86/kernel/alt-syscall.c b/arch/x86/kernel/alt-syscall.c |
| new file mode 100644 |
| index 000000000000..09e7ed7e3f3e |
| --- /dev/null |
| +++ b/arch/x86/kernel/alt-syscall.c |
| @@ -0,0 +1,70 @@ |
| +#include <linux/sched.h> |
| +#include <linux/mm.h> |
| +#include <linux/kernel.h> |
| +#include <linux/errno.h> |
| +#include <linux/unistd.h> |
| +#include <linux/slab.h> |
| +#include <linux/stddef.h> |
| +#include <linux/syscalls.h> |
| +#include <linux/alt-syscall.h> |
| + |
| +#include <asm/syscall.h> |
| +#include <asm/syscalls.h> |
| + |
| +int arch_dup_sys_call_table(struct alt_sys_call_table *entry) |
| +{ |
| + if (!entry) |
| + return -EINVAL; |
| + /* Table already allocated. */ |
| + if (entry->table) |
| + return -EINVAL; |
| +#ifdef CONFIG_IA32_EMULATION |
| + if (entry->compat_table) |
| + return -EINVAL; |
| +#endif |
| + entry->size = NR_syscalls; |
| + entry->table = kcalloc(entry->size, sizeof(sys_call_ptr_t), |
| + GFP_KERNEL); |
| + if (!entry->table) |
| + goto failed; |
| + |
| + memcpy(entry->table, sys_call_table, |
| + entry->size * sizeof(sys_call_ptr_t)); |
| + |
| +#ifdef CONFIG_IA32_EMULATION |
| + entry->compat_size = IA32_NR_syscalls; |
| + entry->compat_table = kcalloc(entry->compat_size, |
| + sizeof(sys_call_ptr_t), GFP_KERNEL); |
| + if (!entry->compat_table) |
| + goto failed; |
| + memcpy(entry->compat_table, ia32_sys_call_table, |
| + entry->compat_size * sizeof(sys_call_ptr_t)); |
| +#endif |
| + |
| + return 0; |
| + |
| +failed: |
| + entry->size = 0; |
| + kfree(entry->table); |
| + entry->table = NULL; |
| +#ifdef CONFIG_IA32_EMULATION |
| + entry->compat_size = 0; |
| +#endif |
| + return -ENOMEM; |
| +} |
| + |
| +/* Operates on "current", which isn't racey, since it's _in_ a syscall. */ |
| +int arch_set_sys_call_table(struct alt_sys_call_table *entry) |
| +{ |
| + if (!entry) |
| + return -EINVAL; |
| + |
| + current_thread_info()->nr_syscalls = entry->size; |
| + current_thread_info()->sys_call_table = entry->table; |
| +#ifdef CONFIG_IA32_EMULATION |
| + current_thread_info()->ia32_nr_syscalls = entry->compat_size; |
| + current_thread_info()->ia32_sys_call_table = entry->compat_table; |
| +#endif |
| + |
| + return 0; |
| +} |
| diff --git a/security/chromiumos/Kconfig b/security/chromiumos/Kconfig |
| index b2afa0ef24cb..647270062c71 100644 |
| --- a/security/chromiumos/Kconfig |
| +++ b/security/chromiumos/Kconfig |
| @@ -31,7 +31,6 @@ config SECURITY_CHROMIUMOS_NO_UNPRIVILEGED_UNSAFE_MOUNTS |
| config ALT_SYSCALL_CHROMIUMOS |
| bool "Chromium OS Alt-Syscall Tables" |
| depends on ALT_SYSCALL |
| - depends on X86_64 || ARM64 |
| help |
| Register restricted, alternate syscall tables used by Chromium OS |
| using the alt-syscall infrastructure. Alternate syscall tables |
| -- |
| 2.32.0.93.g670b81a890-goog |
| |