Skip to content

Commit 4378a7d

Browse files
mrutland-armwildea01
authored andcommitted
arm64: implement syscall wrappers
To minimize the risk of userspace-controlled values being used under speculation, this patch adds pt_regs based syscall wrappers for arm64, which pass the minimum set of required userspace values to syscall implementations. For each syscall, a wrapper which takes a pt_regs argument is automatically generated, and this extracts the arguments before calling the "real" syscall implementation. Each syscall has three functions generated: * __do_<compat_>sys_<name> is the "real" syscall implementation, with the expected prototype. * __se_<compat_>sys_<name> is the sign-extension/narrowing wrapper, inherited from common code. This takes a series of long parameters, casting each to the requisite types required by the "real" syscall implementation in __do_<compat_>sys_<name>. This wrapper *may* not be necessary on arm64 given the AAPCS rules on unused register bits, but it seemed safer to keep the wrapper for now. * __arm64_<compat_>_sys_<name> takes a struct pt_regs pointer, and extracts *only* the relevant register values, passing these on to the __se_<compat_>sys_<name> wrapper. The syscall invocation code is updated to handle the calling convention required by __arm64_<compat_>_sys_<name>, and passes a single struct pt_regs pointer. The compiler can fold the syscall implementation and its wrappers, such that the overhead of this approach is minimized. Note that we play games with sys_ni_syscall(). It can't be defined with SYSCALL_DEFINE0() because we must avoid the possibility of error injection. Additionally, there are a couple of locations where we need to call it from C code, and we don't (currently) have a ksys_ni_syscall(). While it has no wrapper, passing in a redundant pt_regs pointer is benign per the AAPCS. When ARCH_HAS_SYSCALL_WRAPPER is selected, no prototype is defines for sys_ni_syscall(). Since we need to treat it differently for in-kernel calls and the syscall tables, the prototype is defined as-required. The wrappers are largely the same as their x86 counterparts, but simplified as we don't have a variety of compat calling conventions that require separate stubs. Unlike x86, we have some zero-argument compat syscalls, and must define COMPAT_SYSCALL_DEFINE0() to ensure that these are also given an __arm64_compat_sys_ prefix. Signed-off-by: Mark Rutland <mark.rutland@arm.com> Reviewed-by: Dominik Brodowski <linux@dominikbrodowski.net> Reviewed-by: Catalin Marinas <catalin.marinas@arm.com> Cc: Catalin Marinas <catalin.marinas@arm.com> Cc: Will Deacon <will.deacon@arm.com> Signed-off-by: Will Deacon <will.deacon@arm.com>
1 parent 55f8492 commit 4378a7d

File tree

6 files changed

+101
-8
lines changed

6 files changed

+101
-8
lines changed

arch/arm64/Kconfig

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ config ARM64
2424
select ARCH_HAS_SG_CHAIN
2525
select ARCH_HAS_STRICT_KERNEL_RWX
2626
select ARCH_HAS_STRICT_MODULE_RWX
27+
select ARCH_HAS_SYSCALL_WRAPPER
2728
select ARCH_HAS_TICK_BROADCAST if GENERIC_CLOCKEVENTS_BROADCAST
2829
select ARCH_HAVE_NMI_SAFE_CMPXCHG
2930
select ARCH_INLINE_READ_LOCK if !PREEMPT

arch/arm64/include/asm/syscall.h

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,7 @@
2020
#include <linux/compat.h>
2121
#include <linux/err.h>
2222

23-
typedef long (*syscall_fn_t)(unsigned long, unsigned long,
24-
unsigned long, unsigned long,
25-
unsigned long, unsigned long);
23+
typedef long (*syscall_fn_t)(struct pt_regs *regs);
2624

2725
extern const syscall_fn_t sys_call_table[];
2826

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,80 @@
1+
/* SPDX-License-Identifier: GPL-2.0 */
2+
/*
3+
* syscall_wrapper.h - arm64 specific wrappers to syscall definitions
4+
*
5+
* Based on arch/x86/include/asm_syscall_wrapper.h
6+
*/
7+
8+
#ifndef __ASM_SYSCALL_WRAPPER_H
9+
#define __ASM_SYSCALL_WRAPPER_H
10+
11+
#define SC_ARM64_REGS_TO_ARGS(x, ...) \
12+
__MAP(x,__SC_ARGS \
13+
,,regs->regs[0],,regs->regs[1],,regs->regs[2] \
14+
,,regs->regs[3],,regs->regs[4],,regs->regs[5])
15+
16+
#ifdef CONFIG_COMPAT
17+
18+
#define COMPAT_SYSCALL_DEFINEx(x, name, ...) \
19+
asmlinkage long __arm64_compat_sys##name(const struct pt_regs *regs); \
20+
ALLOW_ERROR_INJECTION(__arm64_compat_sys##name, ERRNO); \
21+
static long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
22+
static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
23+
asmlinkage long __arm64_compat_sys##name(const struct pt_regs *regs) \
24+
{ \
25+
return __se_compat_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__)); \
26+
} \
27+
static long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
28+
{ \
29+
return __do_compat_sys##name(__MAP(x,__SC_DELOUSE,__VA_ARGS__)); \
30+
} \
31+
static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
32+
33+
#define COMPAT_SYSCALL_DEFINE0(sname) \
34+
asmlinkage long __arm64_compat_sys_##sname(void); \
35+
ALLOW_ERROR_INJECTION(__arm64_compat_sys_##sname, ERRNO); \
36+
asmlinkage long __arm64_compat_sys_##sname(void)
37+
38+
#define COND_SYSCALL_COMPAT(name) \
39+
cond_syscall(__arm64_compat_sys_##name);
40+
41+
#define COMPAT_SYS_NI(name) \
42+
SYSCALL_ALIAS(__arm64_compat_sys_##name, sys_ni_posix_timers);
43+
44+
#endif /* CONFIG_COMPAT */
45+
46+
#define __SYSCALL_DEFINEx(x, name, ...) \
47+
asmlinkage long __arm64_sys##name(const struct pt_regs *regs); \
48+
ALLOW_ERROR_INJECTION(__arm64_sys##name, ERRNO); \
49+
static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
50+
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__)); \
51+
asmlinkage long __arm64_sys##name(const struct pt_regs *regs) \
52+
{ \
53+
return __se_sys##name(SC_ARM64_REGS_TO_ARGS(x,__VA_ARGS__)); \
54+
} \
55+
static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
56+
{ \
57+
long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__)); \
58+
__MAP(x,__SC_TEST,__VA_ARGS__); \
59+
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
60+
return ret; \
61+
} \
62+
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
63+
64+
#ifndef SYSCALL_DEFINE0
65+
#define SYSCALL_DEFINE0(sname) \
66+
SYSCALL_METADATA(_##sname, 0); \
67+
asmlinkage long __arm64_sys_##sname(void); \
68+
ALLOW_ERROR_INJECTION(__arm64_sys_##sname, ERRNO); \
69+
asmlinkage long __arm64_sys_##sname(void)
70+
#endif
71+
72+
#ifndef COND_SYSCALL
73+
#define COND_SYSCALL(name) cond_syscall(__arm64_sys_##name)
74+
#endif
75+
76+
#ifndef SYS_NI
77+
#define SYS_NI(name) SYSCALL_ALIAS(__arm64_sys_##name, sys_ni_posix_timers);
78+
#endif
79+
80+
#endif /* __ASM_SYSCALL_WRAPPER_H */

arch/arm64/kernel/sys.c

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,11 +50,17 @@ SYSCALL_DEFINE1(arm64_personality, unsigned int, personality)
5050
/*
5151
* Wrappers to pass the pt_regs argument.
5252
*/
53-
asmlinkage long sys_rt_sigreturn(void);
5453
#define sys_personality sys_arm64_personality
5554

55+
asmlinkage long sys_ni_syscall(const struct pt_regs *);
56+
#define __arm64_sys_ni_syscall sys_ni_syscall
57+
58+
#undef __SYSCALL
59+
#define __SYSCALL(nr, sym) asmlinkage long __arm64_##sym(const struct pt_regs *);
60+
#include <asm/unistd.h>
61+
5662
#undef __SYSCALL
57-
#define __SYSCALL(nr, sym) [nr] = (syscall_fn_t)sym,
63+
#define __SYSCALL(nr, sym) [nr] = (syscall_fn_t)__arm64_##sym,
5864

5965
const syscall_fn_t sys_call_table[__NR_syscalls] = {
6066
[0 ... __NR_syscalls - 1] = (syscall_fn_t)sys_ni_syscall,

arch/arm64/kernel/sys32.c

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,15 @@ COMPAT_SYSCALL_DEFINE6(aarch32_fallocate, int, fd, int, mode,
133133
return ksys_fallocate(fd, mode, arg_u64(offset), arg_u64(len));
134134
}
135135

136+
asmlinkage long sys_ni_syscall(const struct pt_regs *);
137+
#define __arm64_sys_ni_syscall sys_ni_syscall
138+
139+
#undef __SYSCALL
140+
#define __SYSCALL(nr, sym) asmlinkage long __arm64_##sym(const struct pt_regs *);
141+
#include <asm/unistd32.h>
142+
136143
#undef __SYSCALL
137-
#define __SYSCALL(nr, sym) [nr] = (syscall_fn_t)sym,
144+
#define __SYSCALL(nr, sym) [nr] = (syscall_fn_t)__arm64_##sym,
138145

139146
const syscall_fn_t compat_sys_call_table[__NR_compat_syscalls] = {
140147
[0 ... __NR_compat_syscalls - 1] = (syscall_fn_t)sys_ni_syscall,

arch/arm64/kernel/syscall.c

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515

1616
long compat_arm_syscall(struct pt_regs *regs);
1717

18+
long sys_ni_syscall(void);
19+
1820
asmlinkage long do_ni_syscall(struct pt_regs *regs)
1921
{
2022
#ifdef CONFIG_COMPAT
@@ -31,8 +33,7 @@ asmlinkage long do_ni_syscall(struct pt_regs *regs)
3133

3234
static long __invoke_syscall(struct pt_regs *regs, syscall_fn_t syscall_fn)
3335
{
34-
return syscall_fn(regs->regs[0], regs->regs[1], regs->regs[2],
35-
regs->regs[3], regs->regs[4], regs->regs[5]);
36+
return syscall_fn(regs);
3637
}
3738

3839
static void invoke_syscall(struct pt_regs *regs, unsigned int scno,

0 commit comments

Comments
 (0)