blob: 4e1ad36bb3748d5612f8e6000c102e0cc41bb97c [file] [log] [blame]
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