Skip to content

Commit 3aec4ec

Browse files
Brian GerstPeter Zijlstra
authored andcommitted
x86: Rewrite ret_from_fork() in C
When kCFI is enabled, special handling is needed for the indirect call to the kernel thread function. Rewrite the ret_from_fork() function in C so that the compiler can properly handle the indirect call. Suggested-by: Peter Zijlstra (Intel) <peterz@infradead.org> Signed-off-by: Brian Gerst <brgerst@gmail.com> Signed-off-by: Peter Zijlstra (Intel) <peterz@infradead.org> Reviewed-by: Kees Cook <keescook@chromium.org> Reviewed-by: Sami Tolvanen <samitolvanen@google.com> Link: https://lkml.kernel.org/r/20230623225529.34590-3-brgerst@gmail.com
1 parent 81f755d commit 3aec4ec

File tree

4 files changed

+40
-49
lines changed

4 files changed

+40
-49
lines changed

arch/x86/entry/entry_32.S

Lines changed: 8 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -727,36 +727,22 @@ SYM_CODE_END(__switch_to_asm)
727727
* edi: kernel thread arg
728728
*/
729729
.pushsection .text, "ax"
730-
SYM_CODE_START(ret_from_fork)
730+
SYM_CODE_START(ret_from_fork_asm)
731+
movl %esp, %edx /* regs */
732+
731733
/* return address for the stack unwinder */
732734
pushl $.Lsyscall_32_done
733735

734736
FRAME_BEGIN
735-
pushl %eax
736-
call schedule_tail
737+
/* prev already in EAX */
738+
movl %ebx, %ecx /* fn */
739+
pushl %edi /* fn_arg */
740+
call ret_from_fork
737741
addl $4, %esp
738742
FRAME_END
739743

740-
testl %ebx, %ebx
741-
jnz 1f /* kernel threads are uncommon */
742-
743-
2:
744-
/* When we fork, we trace the syscall return in the child, too. */
745-
leal 4(%esp), %eax
746-
call syscall_exit_to_user_mode
747744
RET
748-
749-
/* kernel thread */
750-
1: movl %edi, %eax
751-
CALL_NOSPEC ebx
752-
/*
753-
* A kernel thread is allowed to return here after successfully
754-
* calling kernel_execve(). Exit to userspace to complete the execve()
755-
* syscall.
756-
*/
757-
movl $0, PT_EAX(%esp)
758-
jmp 2b
759-
SYM_CODE_END(ret_from_fork)
745+
SYM_CODE_END(ret_from_fork_asm)
760746
.popsection
761747

762748
SYM_ENTRY(__begin_SYSENTER_singlestep_region, SYM_L_GLOBAL, SYM_A_NONE)

arch/x86/entry/entry_64.S

Lines changed: 8 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -284,36 +284,19 @@ SYM_FUNC_END(__switch_to_asm)
284284
* r12: kernel thread arg
285285
*/
286286
.pushsection .text, "ax"
287-
__FUNC_ALIGN
288-
SYM_CODE_START_NOALIGN(ret_from_fork)
289-
UNWIND_HINT_END_OF_STACK
287+
SYM_CODE_START(ret_from_fork_asm)
288+
UNWIND_HINT_REGS
290289
ANNOTATE_NOENDBR // copy_thread
291290
CALL_DEPTH_ACCOUNT
292-
movq %rax, %rdi
293-
call schedule_tail /* rdi: 'prev' task parameter */
294291

295-
testq %rbx, %rbx /* from kernel_thread? */
296-
jnz 1f /* kernel threads are uncommon */
292+
movq %rax, %rdi /* prev */
293+
movq %rsp, %rsi /* regs */
294+
movq %rbx, %rdx /* fn */
295+
movq %r12, %rcx /* fn_arg */
296+
call ret_from_fork
297297

298-
2:
299-
UNWIND_HINT_REGS
300-
movq %rsp, %rdi
301-
call syscall_exit_to_user_mode /* returns with IRQs disabled */
302298
jmp swapgs_restore_regs_and_return_to_usermode
303-
304-
1:
305-
/* kernel thread */
306-
UNWIND_HINT_END_OF_STACK
307-
movq %r12, %rdi
308-
CALL_NOSPEC rbx
309-
/*
310-
* A kernel thread is allowed to return here after successfully
311-
* calling kernel_execve(). Exit to userspace to complete the execve()
312-
* syscall.
313-
*/
314-
movq $0, RAX(%rsp)
315-
jmp 2b
316-
SYM_CODE_END(ret_from_fork)
299+
SYM_CODE_END(ret_from_fork_asm)
317300
.popsection
318301

319302
.macro DEBUG_ENTRY_ASSERT_IRQS_OFF

arch/x86/include/asm/switch_to.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ struct task_struct *__switch_to_asm(struct task_struct *prev,
1212
__visible struct task_struct *__switch_to(struct task_struct *prev,
1313
struct task_struct *next);
1414

15-
asmlinkage void ret_from_fork(void);
15+
asmlinkage void ret_from_fork_asm(void);
16+
__visible void ret_from_fork(struct task_struct *prev, struct pt_regs *regs,
17+
int (*fn)(void *), void *fn_arg);
1618

1719
/*
1820
* This is the structure pointed to by thread.sp for an inactive task. The

arch/x86/kernel/process.c

Lines changed: 21 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include <linux/static_call.h>
2929
#include <trace/events/power.h>
3030
#include <linux/hw_breakpoint.h>
31+
#include <linux/entry-common.h>
3132
#include <asm/cpu.h>
3233
#include <asm/apic.h>
3334
#include <linux/uaccess.h>
@@ -134,6 +135,25 @@ static int set_new_tls(struct task_struct *p, unsigned long tls)
134135
return do_set_thread_area_64(p, ARCH_SET_FS, tls);
135136
}
136137

138+
__visible void ret_from_fork(struct task_struct *prev, struct pt_regs *regs,
139+
int (*fn)(void *), void *fn_arg)
140+
{
141+
schedule_tail(prev);
142+
143+
/* Is this a kernel thread? */
144+
if (unlikely(fn)) {
145+
fn(fn_arg);
146+
/*
147+
* A kernel thread is allowed to return here after successfully
148+
* calling kernel_execve(). Exit to userspace to complete the
149+
* execve() syscall.
150+
*/
151+
regs->ax = 0;
152+
}
153+
154+
syscall_exit_to_user_mode(regs);
155+
}
156+
137157
int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
138158
{
139159
unsigned long clone_flags = args->flags;
@@ -149,7 +169,7 @@ int copy_thread(struct task_struct *p, const struct kernel_clone_args *args)
149169
frame = &fork_frame->frame;
150170

151171
frame->bp = encode_frame_pointer(childregs);
152-
frame->ret_addr = (unsigned long) ret_from_fork;
172+
frame->ret_addr = (unsigned long) ret_from_fork_asm;
153173
p->thread.sp = (unsigned long) fork_frame;
154174
p->thread.io_bitmap = NULL;
155175
p->thread.iopl_warn = 0;

0 commit comments

Comments
 (0)