diff options
author | Thomas Garnier <thgarnie@google.com> | 2017-06-14 21:12:01 -0400 |
---|---|---|
committer | Thomas Gleixner <tglx@linutronix.de> | 2017-07-08 08:05:32 -0400 |
commit | 5ea0727b163cb5575e36397a12eade68a1f35f24 (patch) | |
tree | 20ce2b7ad1174b0ef87df07c1105bfb0fdd4788c | |
parent | 4422d80ed7d4bdb2d6e9fb890c66c3d9250ba694 (diff) |
x86/syscalls: Check address limit on user-mode return
Ensure the address limit is a user-mode segment before returning to
user-mode. Otherwise a process can corrupt kernel-mode memory and elevate
privileges [1].
The set_fs function sets the TIF_SETFS flag to force a slow path on
return. In the slow path, the address limit is checked to be USER_DS if
needed.
The addr_limit_user_check function is added as a cross-architecture
function to check the address limit.
[1] https://bugs.chromium.org/p/project-zero/issues/detail?id=990
Signed-off-by: Thomas Garnier <thgarnie@google.com>
Signed-off-by: Thomas Gleixner <tglx@linutronix.de>
Cc: Mark Rutland <mark.rutland@arm.com>
Cc: kernel-hardening@lists.openwall.com
Cc: Catalin Marinas <catalin.marinas@arm.com>
Cc: Will Deacon <will.deacon@arm.com>
Cc: David Howells <dhowells@redhat.com>
Cc: Dave Hansen <dave.hansen@intel.com>
Cc: Miroslav Benes <mbenes@suse.cz>
Cc: Chris Metcalf <cmetcalf@mellanox.com>
Cc: Pratyush Anand <panand@redhat.com>
Cc: Russell King <linux@armlinux.org.uk>
Cc: Petr Mladek <pmladek@suse.com>
Cc: Rik van Riel <riel@redhat.com>
Cc: Kees Cook <keescook@chromium.org>
Cc: Arnd Bergmann <arnd@arndb.de>
Cc: Al Viro <viro@zeniv.linux.org.uk>
Cc: Andy Lutomirski <luto@kernel.org>
Cc: Josh Poimboeuf <jpoimboe@redhat.com>
Cc: linux-arm-kernel@lists.infradead.org
Cc: Will Drewry <wad@chromium.org>
Cc: linux-api@vger.kernel.org
Cc: Oleg Nesterov <oleg@redhat.com>
Cc: Andy Lutomirski <luto@amacapital.net>
Cc: Paolo Bonzini <pbonzini@redhat.com>
Link: http://lkml.kernel.org/r/20170615011203.144108-1-thgarnie@google.com
-rw-r--r-- | arch/x86/entry/common.c | 3 | ||||
-rw-r--r-- | arch/x86/include/asm/thread_info.h | 5 | ||||
-rw-r--r-- | arch/x86/include/asm/uaccess.h | 7 | ||||
-rw-r--r-- | include/linux/syscalls.h | 16 |
4 files changed, 29 insertions, 2 deletions
diff --git a/arch/x86/entry/common.c b/arch/x86/entry/common.c index cdefcfdd9e63..03505ffbe1b6 100644 --- a/arch/x86/entry/common.c +++ b/arch/x86/entry/common.c | |||
@@ -23,6 +23,7 @@ | |||
23 | #include <linux/user-return-notifier.h> | 23 | #include <linux/user-return-notifier.h> |
24 | #include <linux/uprobes.h> | 24 | #include <linux/uprobes.h> |
25 | #include <linux/livepatch.h> | 25 | #include <linux/livepatch.h> |
26 | #include <linux/syscalls.h> | ||
26 | 27 | ||
27 | #include <asm/desc.h> | 28 | #include <asm/desc.h> |
28 | #include <asm/traps.h> | 29 | #include <asm/traps.h> |
@@ -183,6 +184,8 @@ __visible inline void prepare_exit_to_usermode(struct pt_regs *regs) | |||
183 | struct thread_info *ti = current_thread_info(); | 184 | struct thread_info *ti = current_thread_info(); |
184 | u32 cached_flags; | 185 | u32 cached_flags; |
185 | 186 | ||
187 | addr_limit_user_check(); | ||
188 | |||
186 | if (IS_ENABLED(CONFIG_PROVE_LOCKING) && WARN_ON(!irqs_disabled())) | 189 | if (IS_ENABLED(CONFIG_PROVE_LOCKING) && WARN_ON(!irqs_disabled())) |
187 | local_irq_disable(); | 190 | local_irq_disable(); |
188 | 191 | ||
diff --git a/arch/x86/include/asm/thread_info.h b/arch/x86/include/asm/thread_info.h index e00e1bd6e7b3..5161da1a0fa0 100644 --- a/arch/x86/include/asm/thread_info.h +++ b/arch/x86/include/asm/thread_info.h | |||
@@ -98,6 +98,7 @@ struct thread_info { | |||
98 | #define TIF_SYSCALL_TRACEPOINT 28 /* syscall tracepoint instrumentation */ | 98 | #define TIF_SYSCALL_TRACEPOINT 28 /* syscall tracepoint instrumentation */ |
99 | #define TIF_ADDR32 29 /* 32-bit address space on 64 bits */ | 99 | #define TIF_ADDR32 29 /* 32-bit address space on 64 bits */ |
100 | #define TIF_X32 30 /* 32-bit native x86-64 binary */ | 100 | #define TIF_X32 30 /* 32-bit native x86-64 binary */ |
101 | #define TIF_FSCHECK 31 /* Check FS is USER_DS on return */ | ||
101 | 102 | ||
102 | #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) | 103 | #define _TIF_SYSCALL_TRACE (1 << TIF_SYSCALL_TRACE) |
103 | #define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) | 104 | #define _TIF_NOTIFY_RESUME (1 << TIF_NOTIFY_RESUME) |
@@ -122,6 +123,7 @@ struct thread_info { | |||
122 | #define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) | 123 | #define _TIF_SYSCALL_TRACEPOINT (1 << TIF_SYSCALL_TRACEPOINT) |
123 | #define _TIF_ADDR32 (1 << TIF_ADDR32) | 124 | #define _TIF_ADDR32 (1 << TIF_ADDR32) |
124 | #define _TIF_X32 (1 << TIF_X32) | 125 | #define _TIF_X32 (1 << TIF_X32) |
126 | #define _TIF_FSCHECK (1 << TIF_FSCHECK) | ||
125 | 127 | ||
126 | /* | 128 | /* |
127 | * work to do in syscall_trace_enter(). Also includes TIF_NOHZ for | 129 | * work to do in syscall_trace_enter(). Also includes TIF_NOHZ for |
@@ -137,7 +139,8 @@ struct thread_info { | |||
137 | (_TIF_SYSCALL_TRACE | _TIF_NOTIFY_RESUME | _TIF_SIGPENDING | \ | 139 | (_TIF_SYSCALL_TRACE | _TIF_NOTIFY_RESUME | _TIF_SIGPENDING | \ |
138 | _TIF_NEED_RESCHED | _TIF_SINGLESTEP | _TIF_SYSCALL_EMU | \ | 140 | _TIF_NEED_RESCHED | _TIF_SINGLESTEP | _TIF_SYSCALL_EMU | \ |
139 | _TIF_SYSCALL_AUDIT | _TIF_USER_RETURN_NOTIFY | _TIF_UPROBE | \ | 141 | _TIF_SYSCALL_AUDIT | _TIF_USER_RETURN_NOTIFY | _TIF_UPROBE | \ |
140 | _TIF_PATCH_PENDING | _TIF_NOHZ | _TIF_SYSCALL_TRACEPOINT) | 142 | _TIF_PATCH_PENDING | _TIF_NOHZ | _TIF_SYSCALL_TRACEPOINT | \ |
143 | _TIF_FSCHECK) | ||
141 | 144 | ||
142 | /* flags to check in __switch_to() */ | 145 | /* flags to check in __switch_to() */ |
143 | #define _TIF_WORK_CTXSW \ | 146 | #define _TIF_WORK_CTXSW \ |
diff --git a/arch/x86/include/asm/uaccess.h b/arch/x86/include/asm/uaccess.h index a059aac9e937..11433f9018e2 100644 --- a/arch/x86/include/asm/uaccess.h +++ b/arch/x86/include/asm/uaccess.h | |||
@@ -26,7 +26,12 @@ | |||
26 | 26 | ||
27 | #define get_ds() (KERNEL_DS) | 27 | #define get_ds() (KERNEL_DS) |
28 | #define get_fs() (current->thread.addr_limit) | 28 | #define get_fs() (current->thread.addr_limit) |
29 | #define set_fs(x) (current->thread.addr_limit = (x)) | 29 | static inline void set_fs(mm_segment_t fs) |
30 | { | ||
31 | current->thread.addr_limit = fs; | ||
32 | /* On user-mode return, check fs is correct */ | ||
33 | set_thread_flag(TIF_FSCHECK); | ||
34 | } | ||
30 | 35 | ||
31 | #define segment_eq(a, b) ((a).seg == (b).seg) | 36 | #define segment_eq(a, b) ((a).seg == (b).seg) |
32 | 37 | ||
diff --git a/include/linux/syscalls.h b/include/linux/syscalls.h index 980c3c9b06f8..ac0cf6fb25d6 100644 --- a/include/linux/syscalls.h +++ b/include/linux/syscalls.h | |||
@@ -206,6 +206,22 @@ extern struct trace_event_functions exit_syscall_print_funcs; | |||
206 | } \ | 206 | } \ |
207 | static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)) | 207 | static inline long SYSC##name(__MAP(x,__SC_DECL,__VA_ARGS__)) |
208 | 208 | ||
209 | #ifdef TIF_FSCHECK | ||
210 | /* | ||
211 | * Called before coming back to user-mode. Returning to user-mode with an | ||
212 | * address limit different than USER_DS can allow to overwrite kernel memory. | ||
213 | */ | ||
214 | static inline void addr_limit_user_check(void) | ||
215 | { | ||
216 | |||
217 | if (!test_thread_flag(TIF_FSCHECK)) | ||
218 | return; | ||
219 | |||
220 | BUG_ON(!segment_eq(get_fs(), USER_DS)); | ||
221 | clear_thread_flag(TIF_FSCHECK); | ||
222 | } | ||
223 | #endif | ||
224 | |||
209 | asmlinkage long sys32_quotactl(unsigned int cmd, const char __user *special, | 225 | asmlinkage long sys32_quotactl(unsigned int cmd, const char __user *special, |
210 | qid_t id, void __user *addr); | 226 | qid_t id, void __user *addr); |
211 | asmlinkage long sys_time(time_t __user *tloc); | 227 | asmlinkage long sys_time(time_t __user *tloc); |