![Richard Palethorpe](/assets/img/avatar_default.png)
commit 56062d60f117dccfb5281869e0ab61e090baf864 upstream. Presently ia32 registers stored in ptregs are unconditionally cast to unsigned int by the ia32 stub. They are then cast to long when passed to __se_sys*, but will not be sign extended. This takes the sign of the syscall argument into account in the ia32 stub. It still casts to unsigned int to avoid implementation specific behavior. However then casts to int or unsigned int as necessary. So that the following cast to long sign extends the value. This fixes the io_pgetevents02 LTP test when compiled with -m32. Presently the systemcall io_pgetevents_time64() unexpectedly accepts -1 for the maximum number of events. It doesn't appear other systemcalls with signed arguments are effected because they all have compat variants defined and wired up. Fixes: ebeb8c82ffaf ("syscalls/x86: Use 'struct pt_regs' based syscall calling for IA32_EMULATION and x32") Suggested-by: Arnd Bergmann <arnd@arndb.de> Signed-off-by: Richard Palethorpe <rpalethorpe@suse.com> Signed-off-by: Nikolay Borisov <nik.borisov@suse.com> Signed-off-by: Thomas Gleixner <tglx@linutronix.de> Reviewed-by: Arnd Bergmann <arnd@arndb.de> Cc: stable@vger.kernel.org Link: https://lore.kernel.org/r/20240110130122.3836513-1-nik.borisov@suse.com Link: https://lore.kernel.org/ltp/20210921130127.24131-1-rpalethorpe@suse.com/ Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
289 lines
9.7 KiB
C
Executable file
289 lines
9.7 KiB
C
Executable file
/* SPDX-License-Identifier: GPL-2.0 */
|
|
/*
|
|
* syscall_wrapper.h - x86 specific wrappers to syscall definitions
|
|
*/
|
|
|
|
#ifndef _ASM_X86_SYSCALL_WRAPPER_H
|
|
#define _ASM_X86_SYSCALL_WRAPPER_H
|
|
|
|
struct pt_regs;
|
|
|
|
extern long __x64_sys_ni_syscall(const struct pt_regs *regs);
|
|
extern long __ia32_sys_ni_syscall(const struct pt_regs *regs);
|
|
|
|
/*
|
|
* Instead of the generic __SYSCALL_DEFINEx() definition, the x86 version takes
|
|
* struct pt_regs *regs as the only argument of the syscall stub(s) named as:
|
|
* __x64_sys_*() - 64-bit native syscall
|
|
* __ia32_sys_*() - 32-bit native syscall or common compat syscall
|
|
* __ia32_compat_sys_*() - 32-bit compat syscall
|
|
* __x32_compat_sys_*() - 64-bit X32 compat syscall
|
|
*
|
|
* The registers are decoded according to the ABI:
|
|
* 64-bit: RDI, RSI, RDX, R10, R8, R9
|
|
* 32-bit: EBX, ECX, EDX, ESI, EDI, EBP
|
|
*
|
|
* The stub then passes the decoded arguments to the __se_sys_*() wrapper to
|
|
* perform sign-extension (omitted for zero-argument syscalls). Finally the
|
|
* arguments are passed to the __do_sys_*() function which is the actual
|
|
* syscall. These wrappers are marked as inline so the compiler can optimize
|
|
* the functions where appropriate.
|
|
*
|
|
* Example assembly (slightly re-ordered for better readability):
|
|
*
|
|
* <__x64_sys_recv>: <-- syscall with 4 parameters
|
|
* callq <__fentry__>
|
|
*
|
|
* mov 0x70(%rdi),%rdi <-- decode regs->di
|
|
* mov 0x68(%rdi),%rsi <-- decode regs->si
|
|
* mov 0x60(%rdi),%rdx <-- decode regs->dx
|
|
* mov 0x38(%rdi),%rcx <-- decode regs->r10
|
|
*
|
|
* xor %r9d,%r9d <-- clear %r9
|
|
* xor %r8d,%r8d <-- clear %r8
|
|
*
|
|
* callq __sys_recvfrom <-- do the actual work in __sys_recvfrom()
|
|
* which takes 6 arguments
|
|
*
|
|
* cltq <-- extend return value to 64-bit
|
|
* retq <-- return
|
|
*
|
|
* This approach avoids leaking random user-provided register content down
|
|
* the call chain.
|
|
*/
|
|
|
|
/* Mapping of registers to parameters for syscalls on x86-64 and x32 */
|
|
#define SC_X86_64_REGS_TO_ARGS(x, ...) \
|
|
__MAP(x,__SC_ARGS \
|
|
,,regs->di,,regs->si,,regs->dx \
|
|
,,regs->r10,,regs->r8,,regs->r9) \
|
|
|
|
|
|
/* SYSCALL_PT_ARGS is Adapted from s390x */
|
|
#define SYSCALL_PT_ARG6(m, t1, t2, t3, t4, t5, t6) \
|
|
SYSCALL_PT_ARG5(m, t1, t2, t3, t4, t5), m(t6, (regs->bp))
|
|
#define SYSCALL_PT_ARG5(m, t1, t2, t3, t4, t5) \
|
|
SYSCALL_PT_ARG4(m, t1, t2, t3, t4), m(t5, (regs->di))
|
|
#define SYSCALL_PT_ARG4(m, t1, t2, t3, t4) \
|
|
SYSCALL_PT_ARG3(m, t1, t2, t3), m(t4, (regs->si))
|
|
#define SYSCALL_PT_ARG3(m, t1, t2, t3) \
|
|
SYSCALL_PT_ARG2(m, t1, t2), m(t3, (regs->dx))
|
|
#define SYSCALL_PT_ARG2(m, t1, t2) \
|
|
SYSCALL_PT_ARG1(m, t1), m(t2, (regs->cx))
|
|
#define SYSCALL_PT_ARG1(m, t1) m(t1, (regs->bx))
|
|
#define SYSCALL_PT_ARGS(x, ...) SYSCALL_PT_ARG##x(__VA_ARGS__)
|
|
|
|
#define __SC_COMPAT_CAST(t, a) \
|
|
(__typeof(__builtin_choose_expr(__TYPE_IS_L(t), 0, 0U))) \
|
|
(unsigned int)a
|
|
|
|
/* Mapping of registers to parameters for syscalls on i386 */
|
|
#define SC_IA32_REGS_TO_ARGS(x, ...) \
|
|
SYSCALL_PT_ARGS(x, __SC_COMPAT_CAST, \
|
|
__MAP(x, __SC_TYPE, __VA_ARGS__)) \
|
|
|
|
#define __SYS_STUB0(abi, name) \
|
|
long __##abi##_##name(const struct pt_regs *regs); \
|
|
ALLOW_ERROR_INJECTION(__##abi##_##name, ERRNO); \
|
|
long __##abi##_##name(const struct pt_regs *regs) \
|
|
__alias(__do_##name);
|
|
|
|
#define __SYS_STUBx(abi, name, ...) \
|
|
long __##abi##_##name(const struct pt_regs *regs); \
|
|
ALLOW_ERROR_INJECTION(__##abi##_##name, ERRNO); \
|
|
long __##abi##_##name(const struct pt_regs *regs) \
|
|
{ \
|
|
return __se_##name(__VA_ARGS__); \
|
|
}
|
|
|
|
#define __COND_SYSCALL(abi, name) \
|
|
__weak long __##abi##_##name(const struct pt_regs *__unused) \
|
|
{ \
|
|
return sys_ni_syscall(); \
|
|
}
|
|
|
|
#define __SYS_NI(abi, name) \
|
|
SYSCALL_ALIAS(__##abi##_##name, sys_ni_posix_timers);
|
|
|
|
#ifdef CONFIG_X86_64
|
|
#define __X64_SYS_STUB0(name) \
|
|
__SYS_STUB0(x64, sys_##name)
|
|
|
|
#define __X64_SYS_STUBx(x, name, ...) \
|
|
__SYS_STUBx(x64, sys##name, \
|
|
SC_X86_64_REGS_TO_ARGS(x, __VA_ARGS__))
|
|
|
|
#define __X64_COND_SYSCALL(name) \
|
|
__COND_SYSCALL(x64, sys_##name)
|
|
|
|
#define __X64_SYS_NI(name) \
|
|
__SYS_NI(x64, sys_##name)
|
|
#else /* CONFIG_X86_64 */
|
|
#define __X64_SYS_STUB0(name)
|
|
#define __X64_SYS_STUBx(x, name, ...)
|
|
#define __X64_COND_SYSCALL(name)
|
|
#define __X64_SYS_NI(name)
|
|
#endif /* CONFIG_X86_64 */
|
|
|
|
#if defined(CONFIG_X86_32) || defined(CONFIG_IA32_EMULATION)
|
|
#define __IA32_SYS_STUB0(name) \
|
|
__SYS_STUB0(ia32, sys_##name)
|
|
|
|
#define __IA32_SYS_STUBx(x, name, ...) \
|
|
__SYS_STUBx(ia32, sys##name, \
|
|
SC_IA32_REGS_TO_ARGS(x, __VA_ARGS__))
|
|
|
|
#define __IA32_COND_SYSCALL(name) \
|
|
__COND_SYSCALL(ia32, sys_##name)
|
|
|
|
#define __IA32_SYS_NI(name) \
|
|
__SYS_NI(ia32, sys_##name)
|
|
#else /* CONFIG_X86_32 || CONFIG_IA32_EMULATION */
|
|
#define __IA32_SYS_STUB0(name)
|
|
#define __IA32_SYS_STUBx(x, name, ...)
|
|
#define __IA32_COND_SYSCALL(name)
|
|
#define __IA32_SYS_NI(name)
|
|
#endif /* CONFIG_X86_32 || CONFIG_IA32_EMULATION */
|
|
|
|
#ifdef CONFIG_IA32_EMULATION
|
|
/*
|
|
* For IA32 emulation, we need to handle "compat" syscalls *and* create
|
|
* additional wrappers (aptly named __ia32_sys_xyzzy) which decode the
|
|
* ia32 regs in the proper order for shared or "common" syscalls. As some
|
|
* syscalls may not be implemented, we need to expand COND_SYSCALL in
|
|
* kernel/sys_ni.c and SYS_NI in kernel/time/posix-stubs.c to cover this
|
|
* case as well.
|
|
*/
|
|
#define __IA32_COMPAT_SYS_STUB0(name) \
|
|
__SYS_STUB0(ia32, compat_sys_##name)
|
|
|
|
#define __IA32_COMPAT_SYS_STUBx(x, name, ...) \
|
|
__SYS_STUBx(ia32, compat_sys##name, \
|
|
SC_IA32_REGS_TO_ARGS(x, __VA_ARGS__))
|
|
|
|
#define __IA32_COMPAT_COND_SYSCALL(name) \
|
|
__COND_SYSCALL(ia32, compat_sys_##name)
|
|
|
|
#define __IA32_COMPAT_SYS_NI(name) \
|
|
__SYS_NI(ia32, compat_sys_##name)
|
|
|
|
#else /* CONFIG_IA32_EMULATION */
|
|
#define __IA32_COMPAT_SYS_STUB0(name)
|
|
#define __IA32_COMPAT_SYS_STUBx(x, name, ...)
|
|
#define __IA32_COMPAT_COND_SYSCALL(name)
|
|
#define __IA32_COMPAT_SYS_NI(name)
|
|
#endif /* CONFIG_IA32_EMULATION */
|
|
|
|
|
|
#ifdef CONFIG_X86_X32
|
|
/*
|
|
* For the x32 ABI, we need to create a stub for compat_sys_*() which is aware
|
|
* of the x86-64-style parameter ordering of x32 syscalls. The syscalls common
|
|
* with x86_64 obviously do not need such care.
|
|
*/
|
|
#define __X32_COMPAT_SYS_STUB0(name) \
|
|
__SYS_STUB0(x32, compat_sys_##name)
|
|
|
|
#define __X32_COMPAT_SYS_STUBx(x, name, ...) \
|
|
__SYS_STUBx(x32, compat_sys##name, \
|
|
SC_X86_64_REGS_TO_ARGS(x, __VA_ARGS__))
|
|
|
|
#define __X32_COMPAT_COND_SYSCALL(name) \
|
|
__COND_SYSCALL(x32, compat_sys_##name)
|
|
|
|
#define __X32_COMPAT_SYS_NI(name) \
|
|
__SYS_NI(x32, compat_sys_##name)
|
|
#else /* CONFIG_X86_X32 */
|
|
#define __X32_COMPAT_SYS_STUB0(name)
|
|
#define __X32_COMPAT_SYS_STUBx(x, name, ...)
|
|
#define __X32_COMPAT_COND_SYSCALL(name)
|
|
#define __X32_COMPAT_SYS_NI(name)
|
|
#endif /* CONFIG_X86_X32 */
|
|
|
|
|
|
#ifdef CONFIG_COMPAT
|
|
/*
|
|
* Compat means IA32_EMULATION and/or X86_X32. As they use a different
|
|
* mapping of registers to parameters, we need to generate stubs for each
|
|
* of them.
|
|
*/
|
|
#define COMPAT_SYSCALL_DEFINE0(name) \
|
|
static long \
|
|
__do_compat_sys_##name(const struct pt_regs *__unused); \
|
|
__IA32_COMPAT_SYS_STUB0(name) \
|
|
__X32_COMPAT_SYS_STUB0(name) \
|
|
static long \
|
|
__do_compat_sys_##name(const struct pt_regs *__unused)
|
|
|
|
#define COMPAT_SYSCALL_DEFINEx(x, name, ...) \
|
|
static long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
|
|
static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
|
|
__IA32_COMPAT_SYS_STUBx(x, name, __VA_ARGS__) \
|
|
__X32_COMPAT_SYS_STUBx(x, name, __VA_ARGS__) \
|
|
static long __se_compat_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
|
|
{ \
|
|
return __do_compat_sys##name(__MAP(x,__SC_DELOUSE,__VA_ARGS__));\
|
|
} \
|
|
static inline long __do_compat_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
|
|
|
|
/*
|
|
* As some compat syscalls may not be implemented, we need to expand
|
|
* COND_SYSCALL_COMPAT in kernel/sys_ni.c and COMPAT_SYS_NI in
|
|
* kernel/time/posix-stubs.c to cover this case as well.
|
|
*/
|
|
#define COND_SYSCALL_COMPAT(name) \
|
|
__IA32_COMPAT_COND_SYSCALL(name) \
|
|
__X32_COMPAT_COND_SYSCALL(name)
|
|
|
|
#define COMPAT_SYS_NI(name) \
|
|
__IA32_COMPAT_SYS_NI(name) \
|
|
__X32_COMPAT_SYS_NI(name)
|
|
|
|
#endif /* CONFIG_COMPAT */
|
|
|
|
#define __SYSCALL_DEFINEx(x, name, ...) \
|
|
static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)); \
|
|
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__));\
|
|
__X64_SYS_STUBx(x, name, __VA_ARGS__) \
|
|
__IA32_SYS_STUBx(x, name, __VA_ARGS__) \
|
|
static long __se_sys##name(__MAP(x,__SC_LONG,__VA_ARGS__)) \
|
|
{ \
|
|
long ret = __do_sys##name(__MAP(x,__SC_CAST,__VA_ARGS__));\
|
|
__MAP(x,__SC_TEST,__VA_ARGS__); \
|
|
__PROTECT(x, ret,__MAP(x,__SC_ARGS,__VA_ARGS__)); \
|
|
return ret; \
|
|
} \
|
|
static inline long __do_sys##name(__MAP(x,__SC_DECL,__VA_ARGS__))
|
|
|
|
/*
|
|
* As the generic SYSCALL_DEFINE0() macro does not decode any parameters for
|
|
* obvious reasons, and passing struct pt_regs *regs to it in %rdi does not
|
|
* hurt, we only need to re-define it here to keep the naming congruent to
|
|
* SYSCALL_DEFINEx() -- which is essential for the COND_SYSCALL() and SYS_NI()
|
|
* macros to work correctly.
|
|
*/
|
|
#define SYSCALL_DEFINE0(sname) \
|
|
SYSCALL_METADATA(_##sname, 0); \
|
|
static long __do_sys_##sname(const struct pt_regs *__unused); \
|
|
__X64_SYS_STUB0(sname) \
|
|
__IA32_SYS_STUB0(sname) \
|
|
static long __do_sys_##sname(const struct pt_regs *__unused)
|
|
|
|
#define COND_SYSCALL(name) \
|
|
__X64_COND_SYSCALL(name) \
|
|
__IA32_COND_SYSCALL(name)
|
|
|
|
#define SYS_NI(name) \
|
|
__X64_SYS_NI(name) \
|
|
__IA32_SYS_NI(name)
|
|
|
|
|
|
/*
|
|
* For VSYSCALLS, we need to declare these three syscalls with the new
|
|
* pt_regs-based calling convention for in-kernel use.
|
|
*/
|
|
long __x64_sys_getcpu(const struct pt_regs *regs);
|
|
long __x64_sys_gettimeofday(const struct pt_regs *regs);
|
|
long __x64_sys_time(const struct pt_regs *regs);
|
|
|
|
#endif /* _ASM_X86_SYSCALL_WRAPPER_H */
|