Skip to content

Commit e58bca8

Browse files
wearyzendkalowsk
authored andcommitted
arch: arm: switch to privilege stack in SVC handler
Initialize the privilege stack and switch PSP to it early in the SVC handler to ensure `z_arm_do_syscall` does not start on a user-accessible stack frame. Signed-off-by: Sudan Landge <[email protected]> (cherry picked from commit 319c697)
1 parent 91d1416 commit e58bca8

File tree

3 files changed

+251
-66
lines changed

3 files changed

+251
-66
lines changed

arch/arm/core/cortex_m/swap_helper.S

Lines changed: 212 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -489,20 +489,219 @@ SECTION_FUNC(TEXT, z_arm_svc)
489489
* r8 - saved link register
490490
*/
491491
.L_do_syscall:
492+
/*
493+
* Build a privilege stack frame from the user stack frame, then switch PSP
494+
* to it. This ensures return from SVC does not rely on the user stack.
495+
*
496+
* Layout of privilege stack created from user stack:
497+
*
498+
* +------+-------------------------+------+-------------------------+--------------------------+
499+
* | User stack | Privilege stack | Notes |
500+
* +------+-------------------------+------+-------------------------+--------------------------+
501+
* |Offset| contents |Offset| contents | |
502+
* +------+-------------------------+------+-------------------------+--------------------------+
503+
* | 0 | R0 -> | 0 | R0 | PSP switches from 0th |
504+
* | | | | | offset of user stack to |
505+
* | | | | | 0th offset of priv stack |
506+
* | 4 | R1 -> | 4 | R1 | |
507+
* | 8 | R2 -> | 8 | R2 | |
508+
* | 12 | R3 -> |12 | R3 | |
509+
* | 16 | R12 -> |16 | R12 | |
510+
* | 20 | LR -> |20 | LR | |
511+
* | 24 | Return Address -x> |24 | z_arm_do_syscall |return address from user |
512+
* | | | | |sf is not copied. Instead,|
513+
* | | | | |it is replaced so that |
514+
* | | | | |z_arm_svc returns to |
515+
* | | | | |z_arm_do_syscall. |
516+
* | | | | | |
517+
* | 28 | xPSR (w/ or w/o pad) -> |28 | xPSR (pad bit cleared) |This completes the basic |
518+
* | | | | |exception sf w/ or w/o pad|
519+
* | | | | | |
520+
* | -- | FP regs + FPSCR -> |-- | FP regs + FPSCR |For arch supporting fp |
521+
* | | (w/ or w/o pad) | | |context an additional |
522+
* | | | | |extended sf is copied. |
523+
* |________________________________|______|_________________________|__________________________|
524+
* | | | | |On returning to |
525+
* | | | | |z_arm_do_syscall, the |
526+
* | | | | |above sf has already been |
527+
* | | | | |unstacked and 8B from the |
528+
* | | | | |then sf are used to pass |
529+
* | | | | |original pre-svc sp & the |
530+
* | | | | |return address. |
531+
* | | | | |Note: at the moment |
532+
* | | | | |z_arm_do_syscall also |
533+
* | | | | |expects the return address|
534+
* | | | | |to be set in r8. |
535+
* | | | | | |
536+
* | | | 0 | address that |z_arm_do_syscall expects |
537+
* | | | | z_arm_do_syscall should |the original pre-svc sp at|
538+
* | | | | set as PSP before |0th offset i.e. new sp[0] |
539+
* | | | | returning from svc. |and, |
540+
* | | | | | |
541+
* | | | 4 | Address that |the return address at |
542+
* | | | | z_arm_do_syscall should |sp[4]. Note that this is |
543+
* | | | | return to after handling|the return address copied |
544+
* | | | | svc |from user exception sf[24]|
545+
* | | | | |which was not copied in |
546+
* | | | | |the previous sf. |
547+
* +------+-------------------------+------+-------------------------+--------------------------+
548+
* "sf" in this function is used as abbreviation for "stack frame".
549+
* Note that the "FP regs + FPSCR" are only present if CONFIG_FPU_SHARING=y, and the optional pad
550+
* is only present if PSP was not 8-byte aligned when SVC was executed.
551+
* Also note that FPU cannot be present in ARMv6-M or ARMv8-M Baseline implementations
552+
* (i.e., it may only be present when CONFIG_ARMV7_M_ARMV8_M_MAINLINE is enabled).
553+
*/
554+
/* Start by fetching the top of privileged stack */
492555
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
493-
movs r3, #24
494-
ldr r1, [r0, r3] /* grab address of PC from stack frame */
495-
mov r8, r1
556+
ldr r1, =_kernel
557+
ldr r1, [r1, #_kernel_offset_to_current]
558+
adds r1, r1, #_thread_offset_to_priv_stack_start
559+
ldr r1, [r1] /* bottom of priv stack */
560+
ldr r3, =CONFIG_PRIVILEGED_STACK_SIZE
561+
subs r3, #(_EXC_HW_SAVED_BASIC_SF_SIZE+8) /* 8 for original sp and pc */
562+
add r1, r3
563+
mov ip, r1
496564
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
497-
ldr r8, [r0, #24] /* grab address of PC from stack frame */
565+
ldr ip, =_kernel
566+
ldr ip, [ip, #_kernel_offset_to_current]
567+
ldr ip, [ip, #_thread_offset_to_priv_stack_start] /* bottom of priv stack */
568+
add ip, #CONFIG_PRIVILEGED_STACK_SIZE
569+
#ifdef CONFIG_FPU_SHARING
570+
/* Assess whether svc calling thread had been using the FP registers. */
571+
tst lr, #_EXC_RETURN_FTYPE_Msk
572+
ite eq
573+
moveq r8, #_EXC_HW_SAVED_EXTENDED_SF_SIZE
574+
movne r8, #_EXC_HW_SAVED_BASIC_SF_SIZE
575+
#else
576+
mov r8, #_EXC_HW_SAVED_BASIC_SF_SIZE
498577
#endif
499-
ldr r1, =z_arm_do_syscall
578+
sub ip, #8 /* z_arm_do_syscall will use this to get original sp and pc */
579+
sub ip, r8 /* 32 for basic sf + 72 for the optional esf */
580+
#endif
581+
582+
/*
583+
* At this point:
584+
* r0 has PSP i.e. top of user stack
585+
* ip has top of privilege stack
586+
* r8 has hardware-saved stack frame size (only in case of mainline)
587+
*/
588+
push {r4-r7}
589+
push {r2}
500590
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
501-
str r1, [r0, r3] /* overwrite the PC to point to z_arm_do_syscall */
591+
mov r2, r0 /* safe to use r2 since it is saved on MSP */
592+
593+
/* Check for padding in the sf */
594+
ldr r1, [r0, #_EXC_HW_SAVED_BASIC_SF_XPSR_OFFSET] /* grab xPSR from sf which has the pad bit */
595+
movs r3, #1
596+
/* Check if pad bit 9 is set */
597+
lsls r3, r3, #9
598+
tst r1, r3
599+
beq .L_no_padding
600+
/* special handling for padded sf */
601+
bics r1, r3 /* clear the pad bit (priv stack is aligned and doesn't need it) */
602+
adds r2, #4
603+
.L_no_padding:
604+
/* Calculate original pre-svc user sp which is psp + sf size (+4B if pad bit was set) */
605+
adds r2, #_EXC_HW_SAVED_BASIC_SF_SIZE
606+
mov r3, ip
607+
str r2,[r3, #0]
608+
609+
/* Store the pre-SVC user SP at the offset expected by z_arm_do_syscall,
610+
* as detailed in the table above.
611+
*/
612+
str r2,[r3, #_EXC_HW_SAVED_BASIC_SF_SIZE]
613+
/* sf of priv stack has the same xPSR as user stack but with 9th bit reset */
614+
str r1,[r3, #_EXC_HW_SAVED_BASIC_SF_XPSR_OFFSET]
615+
616+
/* r0-r3, r12, LR from user stack sf are copied to sf of priv stack */
617+
mov r1, r0
618+
mov r2, r3
619+
ldmia r1!, {r4-r7}
620+
stmia r2!, {r4-r7}
621+
ldmia r1!, {r4-r5}
622+
stmia r2!, {r4-r5}
623+
624+
/* Store the svc return address at the offset expected by z_arm_do_syscall,
625+
* as detailed in the table above.
626+
*/
627+
str r5, [r3, #(_EXC_HW_SAVED_BASIC_SF_SIZE+4)]
628+
629+
ldr r1, =z_arm_do_syscall
630+
str r1, [r3, #_EXC_HW_SAVED_BASIC_SF_RETADDR_OFFSET] /* Execution return to z_arm_do_syscall */
631+
ldr r1, [r0, #_EXC_HW_SAVED_BASIC_SF_RETADDR_OFFSET] /* grab address of PC from stack frame */
632+
/* Store the svc return address (i.e. next instr to svc) in r8 as expected by z_arm_do_syscall.
633+
*/
634+
mov r8, r1
635+
502636
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
503-
str r1, [r0, #24] /* overwrite the PC to point to z_arm_do_syscall */
637+
mov r2, r0 /* safe to use r2 since it is saved on MSP */
638+
639+
/* Calculate original pre-svc user sp without pad which is psp + sf size */
640+
add r2, r8
641+
642+
/* Also, check for padding in the sf */
643+
ldr r1, [r0, #_EXC_HW_SAVED_BASIC_SF_XPSR_OFFSET] /* grab xPSR from sf which has the pad bit */
644+
tst r1, #(1<<9) /* Check if pad bit 9 is set */
645+
beq .L_no_padding
646+
bics r1, #(1<<9) /* clear the pad bit (priv stack is aligned and doesn't need it) */
647+
/* Calculate original pre-svc user sp with pad */
648+
add r2, #4
649+
.L_no_padding:
650+
str r2,[ip, #0]
651+
/* Store the pre-SVC user SP at the offset expected by z_arm_do_syscall,
652+
* as detailed in the table above.
653+
*/
654+
str r2,[ip, r8]
655+
str r1,[ip, #_EXC_HW_SAVED_BASIC_SF_XPSR_OFFSET] /* priv sf get user sf xPSR with bit9 reset */
656+
657+
/* r0-r3, r12, LR from user stack sf are copied to sf of priv stack */
658+
mov r1, r0
659+
mov r2, ip
660+
ldmia r1!, {r4-r7}
661+
stmia r2!, {r4-r7}
662+
ldmia r1!, {r4-r5}
663+
stmia r2!, {r4-r5}
664+
665+
/* Store the svc return address at the offset expected by z_arm_do_syscall,
666+
* as detailed in the table above.
667+
*/
668+
add r8, #4
669+
str r5, [ip, r8]
670+
671+
ldr r1, =z_arm_do_syscall
672+
str r1, [ip, #_EXC_HW_SAVED_BASIC_SF_RETADDR_OFFSET] /* Execution return to z_arm_do_syscall */
673+
ldr r1, [r0, #_EXC_HW_SAVED_BASIC_SF_RETADDR_OFFSET] /* grab address of PC from stack frame */
674+
/* Store the svc return address (i.e. next instr to svc) in r8 as expected by z_arm_do_syscall.
675+
*/
676+
mov r8, r1
677+
678+
/* basic stack frame is copied at this point to privilege stack,
679+
* now time to copy the fp context
680+
*/
681+
#ifdef CONFIG_FPU_SHARING
682+
tst lr, #_EXC_RETURN_FTYPE_Msk
683+
bne .L_skip_fp_copy
684+
add r1, r0, #32
685+
add r2, ip, #32
686+
687+
vldmia r1!, {s0-s15}
688+
vstmia r2!, {s0-s15}
689+
690+
/* copy FPSCR + reserved (8 bytes) */
691+
ldmia r1!, {r4, r5}
692+
stmia r2!, {r4, r5}
693+
.L_skip_fp_copy:
504694
#endif
505695

696+
#endif
697+
pop {r2} /* restore CONTROL value */
698+
pop {r4-r7}
699+
700+
/* Point PSP to privilege stack,
701+
* note that r0 still has the old PSP
702+
*/
703+
msr PSP, ip
704+
506705
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
507706
ldr r3, =K_SYSCALL_LIMIT
508707
cmp r6, r3
@@ -556,14 +755,12 @@ SECTION_FUNC(TEXT, z_arm_svc)
556755
isb
557756

558757
#if defined(CONFIG_BUILTIN_STACK_GUARD)
559-
/* Thread is now in privileged mode; after returning from SCVall it
560-
* will use the default (user) stack before switching to the privileged
561-
* stack to execute the system call. We need to protect the user stack
562-
* against stack overflows until this stack transition.
563-
*/
564-
ldr r1, [r0, #_thread_offset_to_stack_info_start] /* stack_info.start */
565-
msr PSPLIM, r1
566-
#endif /* CONFIG_BUILTIN_STACK_GUARD */
758+
/* Set stack pointer limit (needed in privileged mode) */
759+
ldr ip, =_kernel
760+
ldr ip, [ip, #_kernel_offset_to_current]
761+
ldr ip, [ip, #_thread_offset_to_priv_stack_start] /* priv stack ptr */
762+
msr PSPLIM, ip
763+
#endif
567764

568765
/* return from SVC to the modified LR - z_arm_do_syscall */
569766
bx lr

arch/arm/core/userspace.S

Lines changed: 4 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
* Userspace and service handler hooks
33
*
44
* Copyright (c) 2017 Linaro Limited
5+
* Copyright 2025 Arm Limited and/or its affiliates <open-source-[email protected]>
56
*
67
* SPDX-License-Identifier: Apache-2.0
78
*
@@ -308,9 +309,8 @@ SECTION_FUNC(TEXT,z_arm_userspace_enter)
308309
* This function is used to do system calls from unprivileged code. This
309310
* function is responsible for the following:
310311
* 1) Fixing up bad syscalls
311-
* 2) Configuring privileged stack and loading up stack arguments
312-
* 3) Dispatching the system call
313-
* 4) Restoring stack and calling back to the caller of the SVC
312+
* 2) Dispatching the system call
313+
* 3) Restoring stack and calling back to the caller of the SVC
314314
*
315315
*/
316316
SECTION_FUNC(TEXT, z_arm_do_syscall)
@@ -328,41 +328,7 @@ SECTION_FUNC(TEXT, z_arm_do_syscall)
328328
* At this point PSPLIM is already configured to guard the default (user)
329329
* stack, so pushing to the default thread's stack is safe.
330330
*/
331-
#if defined(CONFIG_ARMV6_M_ARMV8_M_BASELINE)
332-
/* save current stack pointer (user stack) */
333-
mov ip, sp
334-
/* temporarily push to user stack */
335-
push {r0,r1}
336-
/* setup privileged stack */
337-
ldr r0, =_kernel
338-
ldr r0, [r0, #_kernel_offset_to_current]
339-
adds r0, r0, #_thread_offset_to_priv_stack_start
340-
ldr r0, [r0] /* priv stack ptr */
341-
ldr r1, =CONFIG_PRIVILEGED_STACK_SIZE
342-
add r0, r1
343-
344-
/* Store current SP and LR at the beginning of the priv stack */
345-
subs r0, #8
346-
mov r1, ip
347-
str r1, [r0, #0]
348-
mov r1, lr
349-
str r1, [r0, #4]
350-
mov ip, r0
351-
/* Restore user stack and original r0, r1 */
352-
pop {r0, r1}
353-
354-
#elif defined(CONFIG_ARMV7_M_ARMV8_M_MAINLINE)
355-
/* setup privileged stack */
356-
ldr ip, =_kernel
357-
ldr ip, [ip, #_kernel_offset_to_current]
358-
ldr ip, [ip, #_thread_offset_to_priv_stack_start] /* priv stack ptr */
359-
add ip, #CONFIG_PRIVILEGED_STACK_SIZE
360-
361-
/* Store current SP and LR at the beginning of the priv stack */
362-
subs ip, #8
363-
str sp, [ip, #0]
364-
str lr, [ip, #4]
365-
#elif defined(CONFIG_CPU_AARCH32_CORTEX_R)
331+
#if defined(CONFIG_CPU_AARCH32_CORTEX_R)
366332
/*
367333
* The SVC handler has already switched to the privileged stack.
368334
* Store the user SP and LR at the beginning of the priv stack.
@@ -373,11 +339,6 @@ SECTION_FUNC(TEXT, z_arm_do_syscall)
373339
push {ip, lr}
374340
#endif
375341

376-
#if !defined(CONFIG_CPU_AARCH32_CORTEX_R)
377-
/* switch to privileged stack */
378-
msr PSP, ip
379-
#endif
380-
381342
/* Note (applies when using stack limit checking):
382343
* We do not need to lock IRQs after switching PSP to the privileged stack;
383344
* PSPLIM is guarding the default (user) stack, which, by design, is
@@ -386,14 +347,6 @@ SECTION_FUNC(TEXT, z_arm_do_syscall)
386347
* the maximum exception stack frame.
387348
*/
388349

389-
#if defined(CONFIG_BUILTIN_STACK_GUARD)
390-
/* Set stack pointer limit (needed in privileged mode) */
391-
ldr ip, =_kernel
392-
ldr ip, [ip, #_kernel_offset_to_current]
393-
ldr ip, [ip, #_thread_offset_to_priv_stack_start] /* priv stack ptr */
394-
msr PSPLIM, ip
395-
#endif
396-
397350
/*
398351
* r0-r5 contain arguments
399352
* r6 contains call_id

include/zephyr/arch/arm/cortex_m/cpu.h

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,41 @@
2626
#define _EXC_RETURN_SPSEL_Msk (1 << 2)
2727
#define _EXC_RETURN_FTYPE_Msk (1 << 4)
2828

29+
/*
30+
* Cortex-M Exception Stack Frame Layouts
31+
*
32+
* When an exception is taken, the processor automatically pushes
33+
* registers to the current stack. The layout depends on whether
34+
* the FPU is active.
35+
*/
36+
37+
/* Basic hardware-saved exception stack frame (no FPU context):
38+
* R0-R3 (4 x 4B = 16B)
39+
* R12 (4B)
40+
* LR (4B)
41+
* Return address (4B)
42+
* RETPSR (4B)
43+
*--------------------------
44+
* Total: 32 bytes
45+
*/
46+
#define _EXC_HW_SAVED_BASIC_SF_SIZE (32)
47+
#define _EXC_HW_SAVED_BASIC_SF_RETADDR_OFFSET (24)
48+
#define _EXC_HW_SAVED_BASIC_SF_XPSR_OFFSET (28)
49+
50+
/* Extended hardware saved stack frame consists of:
51+
* R0-R3 (16B)
52+
* R12 (4B)
53+
* LR (R14) (4B)
54+
* Return address (4B)
55+
* RETPSR (4B)
56+
* S0-S15 (16 x 4B = 64B)
57+
* FPSCR (4B)
58+
* Reserved (4B)
59+
*--------------------------
60+
* Total: 104 bytes
61+
*/
62+
#define _EXC_HW_SAVED_EXTENDED_SF_SIZE (104)
63+
2964
#else
3065
#include <stdint.h>
3166

0 commit comments

Comments
 (0)