Skip to content

Commit a23c245

Browse files
Andrew Boieandrewboie
authored andcommitted
userspace: flesh out internal syscall interface
* Instead of a common system call entry function, we instead create a table mapping system call ids to handler skeleton functions which are invoked directly by the architecture code which receives the system call. * system call handler prototype specified. All but the most trivial system calls will implement one of these. They validate all the arguments, including verifying kernel/device object pointers, ensuring that the calling thread has appropriate access to any memory buffers passed in, and performing other parameter checks that the base system call implementation does not check, or only checks with __ASSERT(). It's only possible to install a system call implementation directly inside this table if the implementation has a return value and requires no validation of any of its arguments. A sample handler implementation for k_mutex_unlock() might look like: u32_t _syscall_k_mutex_unlock(u32_t mutex_arg, u32_t arg2, u32_t arg3, u32_t arg4, u32_t arg5, void *ssf) { struct k_mutex *mutex = (struct k_mutex *)mutex_arg; _SYSCALL_ARG1; _SYSCALL_IS_OBJ(mutex, K_OBJ_MUTEX, 0, ssf); _SYSCALL_VERIFY(mutex->lock_count > 0, ssf); _SYSCALL_VERIFY(mutex->owner == _current, ssf); k_mutex_unlock(mutex); return 0; } * the x86 port modified to work with the system call table instead of calling a common handler function. fixed an issue where registers being changed could confuse the compiler has been fixed; all registers, even ones used for parameters, must be preserved across the system call. * a new arch API for producing a kernel oops when validating system call arguments added. The debug information reported will be from the system call site and not inside the handler function. Signed-off-by: Andrew Boie <andrew.p.boie@intel.com>
1 parent 2541f87 commit a23c245

File tree

6 files changed

+239
-27
lines changed

6 files changed

+239
-27
lines changed

arch/x86/core/fatal.c

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,23 @@ FUNC_NORETURN void _NanoFatalErrorHandler(unsigned int reason,
109109
_SysFatalErrorHandler(reason, pEsf);
110110
}
111111

112+
FUNC_NORETURN void _arch_syscall_oops(void *ssf_ptr)
113+
{
114+
struct _x86_syscall_stack_frame *ssf =
115+
(struct _x86_syscall_stack_frame *)ssf_ptr;
116+
NANO_ESF oops_esf = {
117+
.eip = ssf->eip,
118+
.cs = ssf->cs,
119+
.eflags = ssf->eflags
120+
};
121+
122+
if (oops_esf.cs == USER_CODE_SEG) {
123+
oops_esf.esp = ssf->esp;
124+
}
125+
126+
_NanoFatalErrorHandler(_NANO_ERR_KERNEL_OOPS, &oops_esf);
127+
}
128+
112129
#ifdef CONFIG_X86_KERNEL_OOPS
113130
/* The reason code gets pushed onto the stack right before the exception is
114131
* triggered, so it would be after the nano_esf data

arch/x86/core/userspace.S

Lines changed: 43 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -8,19 +8,33 @@
88
#include <arch/x86/asm.h>
99
#include <arch/cpu.h>
1010
#include <offsets_short.h>
11+
#include <syscall.h>
1112

1213
/* Exports */
1314
GTEXT(_x86_syscall_entry_stub)
1415
GTEXT(_x86_userspace_enter)
1516

1617
/* Imports */
17-
GTEXT(_k_syscall_entry)
18+
GTEXT(_k_syscall_table)
1819

1920
/* Landing site for syscall SW IRQ. Marshal arguments and call C function for
20-
* further processing.
21+
* further processing. We're on the kernel stack for the invoking thread.
2122
*/
2223
SECTION_FUNC(TEXT, _x86_syscall_entry_stub)
23-
push %esi /* call_id */
24+
sti /* re-enable interrupts */
25+
cld /* clear direction flag, restored on 'iret' */
26+
27+
/* call_id is in ESI. bounds-check it, must be less than
28+
* K_SYSCALL_LIMIT
29+
*/
30+
cmp $K_SYSCALL_LIMIT, %esi
31+
jae _bad_syscall
32+
33+
_id_ok:
34+
/* Marshal arguments per calling convention to match what is expected
35+
* for _k_syscall_handler_t functions
36+
*/
37+
push %esp /* ssf */
2438
push %edi /* arg5 */
2539
push %ebx /* arg4 */
2640
#ifndef CONFIG_X86_IAMCU
@@ -29,25 +43,44 @@ SECTION_FUNC(TEXT, _x86_syscall_entry_stub)
2943
push %eax /* arg1 */
3044
#endif
3145

32-
call _k_syscall_entry
46+
/* from the call ID in ESI, load EBX with the actual function pointer
47+
* to call by looking it up in the system call dispatch table
48+
*/
49+
xor %edi, %edi
50+
mov _k_syscall_table(%edi, %esi, 4), %ebx
51+
52+
/* Run the handler, which is some entry in _k_syscall_table */
53+
call *%ebx
3354

3455
/* EAX now contains return value. Pop or xor everything else to prevent
3556
* information leak from kernel mode.
3657
*/
3758
#ifndef CONFIG_X86_IAMCU
38-
pop %edx /* old EAX value, discard it */
59+
pop %edx /* old arg1 value, discard it */
3960
pop %edx
4061
pop %ecx
41-
#else
42-
xor %edx, %edx
43-
xor %ecx, %ecx
4462
#endif
4563
pop %ebx
4664
pop %edi
47-
pop %esi
48-
65+
#ifndef CONFIG_X86_IAMCU
66+
/* Discard ssf, no free register to pop it into so we add instead */
67+
add $4, %esp
68+
#else
69+
xor %edx, %edx /* Clean EDX */
70+
pop %ecx /* Clean ECX and get ssf arg off the stack */
71+
#endif
4972
iret
5073

74+
_bad_syscall:
75+
/* ESI had a bogus syscall value in it, replace with the bad syscall
76+
* handler's ID, and put the bad ID as its first argument. This
77+
* clobbers ESI but the bad syscall handler never returns
78+
* anyway, it's going to generate a kernel oops
79+
*/
80+
mov %esi, %eax
81+
mov $K_SYSCALL_BAD, %esi
82+
jmp _id_ok
83+
5184

5285
/* FUNC_NORETURN void _x86_userspace_enter(k_thread_entry_t user_entry,
5386
* void *p1, void *p2, void *p3,

include/arch/x86/arch.h

Lines changed: 42 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,17 @@ typedef struct nanoEsf {
323323
unsigned int eflags;
324324
} NANO_ESF;
325325

326+
327+
struct _x86_syscall_stack_frame {
328+
u32_t eip;
329+
u32_t cs;
330+
u32_t eflags;
331+
332+
/* These are only present if cs = USER_CODE_SEG */
333+
u32_t esp;
334+
u32_t ss;
335+
};
336+
326337
/**
327338
* @brief "interrupt stack frame" (ISF)
328339
*
@@ -545,6 +556,8 @@ extern struct task_state_segment _main_tss;
545556
/* Syscall invocation macros. x86-specific machine constraints used to ensure
546557
* args land in the proper registers, see implementation of
547558
* _x86_syscall_entry_stub in userspace.S
559+
*
560+
* the entry stub clobbers EDX and ECX on IAMCU systems
548561
*/
549562

550563
static inline u32_t _arch_syscall_invoke5(u32_t arg1, u32_t arg2, u32_t arg3,
@@ -554,6 +567,9 @@ static inline u32_t _arch_syscall_invoke5(u32_t arg1, u32_t arg2, u32_t arg3,
554567

555568
__asm__ volatile("int $0x80"
556569
: "=a" (ret)
570+
#ifdef CONFIG_X86_IAMCU
571+
, "=d" (arg2), "=c" (arg3)
572+
#endif
557573
: "S" (call_id), "a" (arg1), "d" (arg2),
558574
"c" (arg3), "b" (arg4), "D" (arg5));
559575
return ret;
@@ -566,8 +582,11 @@ static inline u32_t _arch_syscall_invoke4(u32_t arg1, u32_t arg2, u32_t arg3,
566582

567583
__asm__ volatile("int $0x80"
568584
: "=a" (ret)
569-
: "S" (call_id), "a" (arg1), "d" (arg2),
570-
"c" (arg3), "b" (arg4));
585+
#ifdef CONFIG_X86_IAMCU
586+
, "=d" (arg2), "=c" (arg3)
587+
#endif
588+
: "S" (call_id), "a" (arg1), "d" (arg2), "c" (arg3),
589+
"b" (arg4));
571590
return ret;
572591
}
573592

@@ -578,6 +597,9 @@ static inline u32_t _arch_syscall_invoke3(u32_t arg1, u32_t arg2, u32_t arg3,
578597

579598
__asm__ volatile("int $0x80"
580599
: "=a" (ret)
600+
#ifdef CONFIG_X86_IAMCU
601+
, "=d" (arg2), "=c" (arg3)
602+
#endif
581603
: "S" (call_id), "a" (arg1), "d" (arg2), "c" (arg3));
582604
return ret;
583605
}
@@ -588,7 +610,14 @@ static inline u32_t _arch_syscall_invoke2(u32_t arg1, u32_t arg2, u32_t call_id)
588610

589611
__asm__ volatile("int $0x80"
590612
: "=a" (ret)
591-
: "S" (call_id), "a" (arg1), "d" (arg2));
613+
#ifdef CONFIG_X86_IAMCU
614+
, "=d" (arg2)
615+
#endif
616+
: "S" (call_id), "a" (arg1), "d" (arg2)
617+
#ifdef CONFIG_X86_IAMCU
618+
: "ecx"
619+
#endif
620+
);
592621
return ret;
593622
}
594623

@@ -598,7 +627,11 @@ static inline u32_t _arch_syscall_invoke1(u32_t arg1, u32_t call_id)
598627

599628
__asm__ volatile("int $0x80"
600629
: "=a" (ret)
601-
: "S" (call_id), "a" (arg1));
630+
: "S" (call_id), "a" (arg1)
631+
#ifdef CONFIG_X86_IAMCU
632+
: "edx", "ecx"
633+
#endif
634+
);
602635
return ret;
603636
}
604637

@@ -608,7 +641,11 @@ static inline u32_t _arch_syscall_invoke0(u32_t call_id)
608641

609642
__asm__ volatile("int $0x80"
610643
: "=a" (ret)
611-
: "S" (call_id));
644+
: "S" (call_id)
645+
#ifdef CONFIG_X86_IAMCU
646+
: "edx", "ecx"
647+
#endif
648+
);
612649
return ret;
613650
}
614651

include/syscall.h

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
/*
2+
* Copyright (c) 2017, Intel Corporation
3+
*
4+
* SPDX-License-Identifier: Apache 2.0
5+
*/
6+
7+
8+
#ifndef _ZEPHYR_SYSCALL_H_
9+
#define _ZEPHYR_SYSCALL_H_
10+
11+
/* Fixed system call IDs. We use #defines instead of enumeration so that if
12+
* system calls are retired it does not shift the IDs of other system calls.
13+
*/
14+
#define K_SYSCALL_BAD 0
15+
16+
#define K_SYSCALL_LIMIT 1
17+
18+
#ifndef _ASMLANGUAGE
19+
#include <misc/printk.h>
20+
21+
/**
22+
* @typedef _k_syscall_handler_t
23+
* @brief System call handler function type
24+
*
25+
* These are kernel-side skeleton functions for system calls. They are
26+
* necessary to sanitize the arguments passed into the system call:
27+
*
28+
* - Any kernel object or device pointers are validated with _SYSCALL_IS_OBJ()
29+
* - Any memory buffers passed in are checked to ensure that the calling thread
30+
* actually has access to them
31+
* - Many kernel calls do no sanity checking of parameters other than
32+
* assertions. The handler must check all of these conditions using
33+
* _SYSCALL_ASSERT()
34+
* - If the system call has more then 5 arguments, then arg5 will be a pointer
35+
* to some struct containing arguments 5+. The struct itself needs to be
36+
* validated like any other buffer passed in from userspace, and its members
37+
* individually validated (if necessary) and then passed to the real
38+
* implementation like normal arguments
39+
*
40+
* Even if the system call implementation has no return value, these always
41+
* return something, even 0, to prevent register leakage to userspace.
42+
*
43+
* Once everything has been validated, the real implementation will be executed.
44+
*
45+
* @param arg1 system call argument 1
46+
* @param arg2 system call argument 2
47+
* @param arg3 system call argument 3
48+
* @param arg4 system call argument 4
49+
* @param arg5 system call argument 5
50+
* @param ssf System call stack frame pointer. Used to generate kernel oops
51+
* via _arch_syscall_oops_at(). Contents are arch-specific.
52+
* @return system call return value, or 0 if the system call implementation
53+
* return void
54+
*
55+
*/
56+
typedef u32_t (*_k_syscall_handler_t)(u32_t arg1, u32_t arg2, u32_t arg3,
57+
u32_t arg4, u32_t arg5, void *ssf);
58+
59+
60+
extern const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT];
61+
62+
63+
/**
64+
* @brief Runtime expression check for system call arguments
65+
*
66+
* Used in handler functions to perform various runtime checks on arguments,
67+
* and generate a kernel oops if anything is not expected
68+
*
69+
* @param expr Boolean expression to verify, a false result will trigger an
70+
* oops
71+
* @param ssf Syscall stack frame argument passed to the handler function
72+
*/
73+
#define _SYSCALL_VERIFY(expr, ssf) \
74+
do { \
75+
if (!(expr)) { \
76+
printk("FATAL: syscall failed check: " #expr "\n"); \
77+
_arch_syscall_oops(ssf); \
78+
} \
79+
} while (0)
80+
81+
/**
82+
* @brief Runtime check that a pointer is a kernel object of expected type
83+
*
84+
* Passes along arguments to _k_object_validate() and triggers a kernel oops
85+
* if the object wasn't valid or had incorrect permissions.
86+
*
87+
* @param ptr Untrusted kernel object pointer
88+
* @param type Expected kernel object type
89+
* @param init Whether this is an init function handler
90+
* @param ssf Syscall stack frame argument passed to the handler function
91+
*/
92+
#define _SYSCALL_IS_OBJ(ptr, type, init, ssf) \
93+
_SYSCALL_VERIFY(!_k_object_validate((void *)ptr, type, init), ssf)
94+
95+
/* Convenience macros for handler implementations */
96+
#define _SYSCALL_ARG0 ARG_UNUSED(arg1); ARG_UNUSED(arg2); ARG_UNUSED(arg3); \
97+
ARG_UNUSED(arg4); ARH_UNUSED(arg5)
98+
99+
#define _SYSCALL_ARG1 ARG_UNUSED(arg2); ARG_UNUSED(arg3); ARG_UNUSED(arg4); \
100+
ARG_UNUSED(arg5)
101+
102+
#define _SYSCALL_ARG2 ARG_UNUSED(arg3); ARG_UNUSED(arg4); ARG_UNUSED(arg5)
103+
104+
#define _SYSCALL_ARG3 ARG_UNUSED(arg4); ARG_UNUSED(arg5)
105+
106+
#define _SYSCALL_ARG4 ARG_UNUSED(arg5)
107+
108+
109+
#endif /* _ASMLANGUAGE */
110+
111+
#endif /* _ZEPHYR_SYSCALL_H_ */

kernel/include/nano_internal.h

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,8 +133,22 @@ extern FUNC_NORETURN
133133
void _arch_user_mode_enter(k_thread_entry_t user_entry, void *p1, void *p2,
134134
void *p3);
135135

136-
extern u32_t _k_syscall_entry(u32_t arg1, u32_t arg2, u32_t arg3, u32_t arg4,
137-
u32_t arg5, u32_t call_id);
136+
137+
/**
138+
* @brief Induce a kernel oops that appears to come from a specific location
139+
*
140+
* Normally, k_oops() generates an exception that appears to come from the
141+
* call site of the k_oops() itself.
142+
*
143+
* However, when validating arguments to a system call, if there are problems
144+
* we want the oops to appear to come from where the system call was invoked
145+
* and not inside the validation function.
146+
*
147+
* @param ssf System call stack frame pointer. This gets passed as an argument
148+
* to _k_syscall_handler_t functions and its contents are completely
149+
* architecture specific.
150+
*/
151+
extern FUNC_NORETURN void _arch_syscall_oops(void *ssf);
138152
#endif /* CONFIG_USERSPACE */
139153

140154
/* set and clear essential fiber/task flag */

kernel/userspace.c

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
#include <kernel_structs.h>
1212
#include <sys_io.h>
1313
#include <ksched.h>
14+
#include <syscall.h>
1415

1516
/**
1617
* Kernel object validation function
@@ -181,16 +182,15 @@ void _k_object_init(void *object)
181182
ko->flags |= K_OBJ_FLAG_INITIALIZED;
182183
}
183184

184-
185-
u32_t _k_syscall_entry(u32_t arg1, u32_t arg2, u32_t arg3, u32_t arg4,
186-
u32_t arg5, u32_t call_id)
185+
static u32_t _syscall_bad_handler(u32_t bad_id, u32_t arg2, u32_t arg3,
186+
u32_t arg4, u32_t arg5, void *ssf)
187187
{
188-
/* A real implementation will figure out what function to call
189-
* based on call_id, validate arguments, perform any other runtime
190-
* checks needed, and call into the appropriate kernel function.
191-
*/
192-
__ASSERT(0, "system calls are unimplemented");
193-
194-
return 0;
188+
printk("Bad system call id %u invoked\n", bad_id);
189+
_arch_syscall_oops(ssf);
190+
CODE_UNREACHABLE;
195191
}
196192

193+
/* This table will eventually be generated by a script, placeholder for now */
194+
const _k_syscall_handler_t _k_syscall_table[K_SYSCALL_LIMIT] = {
195+
[K_SYSCALL_BAD] = _syscall_bad_handler,
196+
};

0 commit comments

Comments
 (0)