Skip to content

Commit b6e6cc1

Browse files
committed
Merge tag 'x86_urgent_for_6.5_rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip
Pull x86 CFI fixes from Peter Zijlstra: "Fix kCFI/FineIBT weaknesses The primary bug Alyssa noticed was that with FineIBT enabled function prologues have a spurious ENDBR instruction: __cfi_foo: endbr64 subl $hash, %r10d jz 1f ud2 nop 1: foo: endbr64 <--- *sadface* This means that any indirect call that fails to target the __cfi symbol and instead targets (the regular old) foo+0, will succeed due to that second ENDBR. Fixing this led to the discovery of a single indirect call that was still doing this: ret_from_fork(). Since that's an assembly stub the compiler would not generate the proper kCFI indirect call magic and it would not get patched. Brian came up with the most comprehensive fix -- convert the thing to C with only a very thin asm wrapper. This ensures the kernel thread boostrap is a proper kCFI call. While discussing all this, Kees noted that kCFI hashes could/should be poisoned to seal all functions whose address is never taken, further limiting the valid kCFI targets -- much like we already do for IBT. So what was a 'simple' observation and fix cascaded into a bunch of inter-related CFI infrastructure fixes" * tag 'x86_urgent_for_6.5_rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/tip/tip: x86/cfi: Only define poison_cfi() if CONFIG_X86_KERNEL_IBT=y x86/fineibt: Poison ENDBR at +0 x86: Rewrite ret_from_fork() in C x86/32: Remove schedule_tail_wrapper() x86/cfi: Extend ENDBR sealing to kCFI x86/alternative: Rename apply_ibt_endbr() x86/cfi: Extend {JMP,CAKK}_NOSPEC comment
2 parents be522ac + 535d0ae commit b6e6cc1

File tree

10 files changed

+120
-75
lines changed

10 files changed

+120
-75
lines changed

arch/um/kernel/um_arch.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,7 @@ void __init arch_cpu_finalize_init(void)
437437
os_check_bugs();
438438
}
439439

440-
void apply_ibt_endbr(s32 *start, s32 *end)
440+
void apply_seal_endbr(s32 *start, s32 *end)
441441
{
442442
}
443443

arch/x86/entry/entry_32.S

Lines changed: 13 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -719,26 +719,6 @@ SYM_CODE_START(__switch_to_asm)
719719
SYM_CODE_END(__switch_to_asm)
720720
.popsection
721721

722-
/*
723-
* The unwinder expects the last frame on the stack to always be at the same
724-
* offset from the end of the page, which allows it to validate the stack.
725-
* Calling schedule_tail() directly would break that convention because its an
726-
* asmlinkage function so its argument has to be pushed on the stack. This
727-
* wrapper creates a proper "end of stack" frame header before the call.
728-
*/
729-
.pushsection .text, "ax"
730-
SYM_FUNC_START(schedule_tail_wrapper)
731-
FRAME_BEGIN
732-
733-
pushl %eax
734-
call schedule_tail
735-
popl %eax
736-
737-
FRAME_END
738-
RET
739-
SYM_FUNC_END(schedule_tail_wrapper)
740-
.popsection
741-
742722
/*
743723
* A newly forked process directly context switches into this address.
744724
*
@@ -747,29 +727,22 @@ SYM_FUNC_END(schedule_tail_wrapper)
747727
* edi: kernel thread arg
748728
*/
749729
.pushsection .text, "ax"
750-
SYM_CODE_START(ret_from_fork)
751-
call schedule_tail_wrapper
730+
SYM_CODE_START(ret_from_fork_asm)
731+
movl %esp, %edx /* regs */
752732

753-
testl %ebx, %ebx
754-
jnz 1f /* kernel threads are uncommon */
733+
/* return address for the stack unwinder */
734+
pushl $.Lsyscall_32_done
755735

756-
2:
757-
/* When we fork, we trace the syscall return in the child, too. */
758-
movl %esp, %eax
759-
call syscall_exit_to_user_mode
760-
jmp .Lsyscall_32_done
736+
FRAME_BEGIN
737+
/* prev already in EAX */
738+
movl %ebx, %ecx /* fn */
739+
pushl %edi /* fn_arg */
740+
call ret_from_fork
741+
addl $4, %esp
742+
FRAME_END
761743

762-
/* kernel thread */
763-
1: movl %edi, %eax
764-
CALL_NOSPEC ebx
765-
/*
766-
* A kernel thread is allowed to return here after successfully
767-
* calling kernel_execve(). Exit to userspace to complete the execve()
768-
* syscall.
769-
*/
770-
movl $0, PT_EAX(%esp)
771-
jmp 2b
772-
SYM_CODE_END(ret_from_fork)
744+
RET
745+
SYM_CODE_END(ret_from_fork_asm)
773746
.popsection
774747

775748
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/alternative.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ extern void alternative_instructions(void);
9696
extern void apply_alternatives(struct alt_instr *start, struct alt_instr *end);
9797
extern void apply_retpolines(s32 *start, s32 *end);
9898
extern void apply_returns(s32 *start, s32 *end);
99-
extern void apply_ibt_endbr(s32 *start, s32 *end);
99+
extern void apply_seal_endbr(s32 *start, s32 *end);
100100
extern void apply_fineibt(s32 *start_retpoline, s32 *end_retpoine,
101101
s32 *start_cfi, s32 *end_cfi);
102102

arch/x86/include/asm/ibt.h

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
/*
3535
* Create a dummy function pointer reference to prevent objtool from marking
3636
* the function as needing to be "sealed" (i.e. ENDBR converted to NOP by
37-
* apply_ibt_endbr()).
37+
* apply_seal_endbr()).
3838
*/
3939
#define IBT_NOSEAL(fname) \
4040
".pushsection .discard.ibt_endbr_noseal\n\t" \

arch/x86/include/asm/nospec-branch.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -234,6 +234,10 @@
234234
* JMP_NOSPEC and CALL_NOSPEC macros can be used instead of a simple
235235
* indirect jmp/call which may be susceptible to the Spectre variant 2
236236
* attack.
237+
*
238+
* NOTE: these do not take kCFI into account and are thus not comparable to C
239+
* indirect calls, take care when using. The target of these should be an ENDBR
240+
* instruction irrespective of kCFI.
237241
*/
238242
.macro JMP_NOSPEC reg:req
239243
#ifdef CONFIG_RETPOLINE

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/alternative.c

Lines changed: 67 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -778,6 +778,8 @@ void __init_or_module noinline apply_returns(s32 *start, s32 *end) { }
778778

779779
#ifdef CONFIG_X86_KERNEL_IBT
780780

781+
static void poison_cfi(void *addr);
782+
781783
static void __init_or_module poison_endbr(void *addr, bool warn)
782784
{
783785
u32 endbr, poison = gen_endbr_poison();
@@ -802,8 +804,11 @@ static void __init_or_module poison_endbr(void *addr, bool warn)
802804

803805
/*
804806
* Generated by: objtool --ibt
807+
*
808+
* Seal the functions for indirect calls by clobbering the ENDBR instructions
809+
* and the kCFI hash value.
805810
*/
806-
void __init_or_module noinline apply_ibt_endbr(s32 *start, s32 *end)
811+
void __init_or_module noinline apply_seal_endbr(s32 *start, s32 *end)
807812
{
808813
s32 *s;
809814

@@ -812,13 +817,13 @@ void __init_or_module noinline apply_ibt_endbr(s32 *start, s32 *end)
812817

813818
poison_endbr(addr, true);
814819
if (IS_ENABLED(CONFIG_FINEIBT))
815-
poison_endbr(addr - 16, false);
820+
poison_cfi(addr - 16);
816821
}
817822
}
818823

819824
#else
820825

821-
void __init_or_module apply_ibt_endbr(s32 *start, s32 *end) { }
826+
void __init_or_module apply_seal_endbr(s32 *start, s32 *end) { }
822827

823828
#endif /* CONFIG_X86_KERNEL_IBT */
824829

@@ -1063,6 +1068,17 @@ static int cfi_rewrite_preamble(s32 *start, s32 *end)
10631068
return 0;
10641069
}
10651070

1071+
static void cfi_rewrite_endbr(s32 *start, s32 *end)
1072+
{
1073+
s32 *s;
1074+
1075+
for (s = start; s < end; s++) {
1076+
void *addr = (void *)s + *s;
1077+
1078+
poison_endbr(addr+16, false);
1079+
}
1080+
}
1081+
10661082
/* .retpoline_sites */
10671083
static int cfi_rand_callers(s32 *start, s32 *end)
10681084
{
@@ -1157,14 +1173,19 @@ static void __apply_fineibt(s32 *start_retpoline, s32 *end_retpoline,
11571173
return;
11581174

11591175
case CFI_FINEIBT:
1176+
/* place the FineIBT preamble at func()-16 */
11601177
ret = cfi_rewrite_preamble(start_cfi, end_cfi);
11611178
if (ret)
11621179
goto err;
11631180

1181+
/* rewrite the callers to target func()-16 */
11641182
ret = cfi_rewrite_callers(start_retpoline, end_retpoline);
11651183
if (ret)
11661184
goto err;
11671185

1186+
/* now that nobody targets func()+0, remove ENDBR there */
1187+
cfi_rewrite_endbr(start_cfi, end_cfi);
1188+
11681189
if (builtin)
11691190
pr_info("Using FineIBT CFI\n");
11701191
return;
@@ -1177,13 +1198,52 @@ static void __apply_fineibt(s32 *start_retpoline, s32 *end_retpoline,
11771198
pr_err("Something went horribly wrong trying to rewrite the CFI implementation.\n");
11781199
}
11791200

1201+
static inline void poison_hash(void *addr)
1202+
{
1203+
*(u32 *)addr = 0;
1204+
}
1205+
1206+
static void poison_cfi(void *addr)
1207+
{
1208+
switch (cfi_mode) {
1209+
case CFI_FINEIBT:
1210+
/*
1211+
* __cfi_\func:
1212+
* osp nopl (%rax)
1213+
* subl $0, %r10d
1214+
* jz 1f
1215+
* ud2
1216+
* 1: nop
1217+
*/
1218+
poison_endbr(addr, false);
1219+
poison_hash(addr + fineibt_preamble_hash);
1220+
break;
1221+
1222+
case CFI_KCFI:
1223+
/*
1224+
* __cfi_\func:
1225+
* movl $0, %eax
1226+
* .skip 11, 0x90
1227+
*/
1228+
poison_hash(addr + 1);
1229+
break;
1230+
1231+
default:
1232+
break;
1233+
}
1234+
}
1235+
11801236
#else
11811237

11821238
static void __apply_fineibt(s32 *start_retpoline, s32 *end_retpoline,
11831239
s32 *start_cfi, s32 *end_cfi, bool builtin)
11841240
{
11851241
}
11861242

1243+
#ifdef CONFIG_X86_KERNEL_IBT
1244+
static void poison_cfi(void *addr) { }
1245+
#endif
1246+
11871247
#endif
11881248

11891249
void apply_fineibt(s32 *start_retpoline, s32 *end_retpoline,
@@ -1565,7 +1625,10 @@ void __init alternative_instructions(void)
15651625
*/
15661626
callthunks_patch_builtin_calls();
15671627

1568-
apply_ibt_endbr(__ibt_endbr_seal, __ibt_endbr_seal_end);
1628+
/*
1629+
* Seal all functions that do not have their address taken.
1630+
*/
1631+
apply_seal_endbr(__ibt_endbr_seal, __ibt_endbr_seal_end);
15691632

15701633
#ifdef CONFIG_SMP
15711634
/* Patch to UP if other cpus not imminent. */

arch/x86/kernel/module.c

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -358,7 +358,7 @@ int module_finalize(const Elf_Ehdr *hdr,
358358
}
359359
if (ibt_endbr) {
360360
void *iseg = (void *)ibt_endbr->sh_addr;
361-
apply_ibt_endbr(iseg, iseg + ibt_endbr->sh_size);
361+
apply_seal_endbr(iseg, iseg + ibt_endbr->sh_size);
362362
}
363363
if (locks) {
364364
void *lseg = (void *)locks->sh_addr;

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)