Skip to contentSkip to conversation

ARM64: SCS: Add gcc plugin to support Shadow Call Stack

PATCH Superseded Dan Li 2 participants 6 messages Oct 13, 2021 – Oct 15, 2021

Subsystems (3)

v4 of 5 Superseded Latest is v5 →

lore
Share & tools
lore

Dan Li

author
#0 lore raw

PATCH v4 1/1 ARM64: SCS: Add gcc plugin to support Shadow Call Stack

This patch supports gcc-based SCS protection on ARM64 by adding a plugin.

For each function that x30 will be pushed onto the stack during execution, this plugin, similar to gcc's pac implementation, will normally:
1) insert "str x30, [x18], #8" at the beginning of function's prologue
2) insert "ldr x30, [x18, #-8]!" immediately before function's epilogue return/sibling calls

If pac is enabled, scs push/pop will be inserted between paciasp/autiasp.

At present, this patch has been successfully compiled in the following gcc versions based on defconfig with kernel 5.14 and startup normally with commands:

make ARCH=arm64 defconfig
./scripts/config -e CONFIG_GCC_PLUGINS -e CONFIG_SHADOW_CALL_STACK \
-e CONFIG_GCC_PLUGIN_SHADOW_CALL_STACK
make ARCH=arm64 CROSS_COMPILE=aarch64-linux-gnu-

Tested gcc version:
* 6.3.1
* 7.3.1
* 7.5.0
* 8.2.1
* 9.2.0
* 10.3.1

---
V4:
- change CC_HAVE_SHADOW_CALL_STACK def_bool

V3:
- fix incorrect description of the compilation option

V2:
- fix incorrect config dependency
- tested against DYNAMIC_FTRACE_WITH_REGS
- add support for sibling call
- add support for ARM64_PTR_AUTH_KERNEL/ARM64_BTI_KERNEL

FYI:
- The kernel compiled by Linaro GCC 7.3-2018.05 runs for more than 10 days, ltp> 24 hours without crash.
- This function can be used to test whether the shadow stack is effective:
//noinline void __noscs scs_test(void)
noinline void scs_test(void)

{
    register unsigned long *sp asm("sp");
    unsigned long * lr = sp + 1;

    asm volatile("":::"x30");
    *lr = 0;
}

when compiled with:
CONFIG_DYNAMIC_FTRACE_WITH_REGS=y
CONFIG_ARM64_PTR_AUTH_KERNEL=y
CONFIG_ARM64_BTI_KERNEL=y

ffff800010013b60 <scs_test>:

ffff800010013b60:       d503245f        bti     c
ffff800010013b64:       d503201f        nop
ffff800010013b68:       d503201f        nop
ffff800010013b6c:       d503233f        paciasp
ffff800010013b70:       f800865e        str     x30, [x18], #8
ffff800010013b74:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010013b78:       910003fd        mov     x29, sp
ffff800010013b7c:       910003e0        mov     x0, sp
ffff800010013b80:       f900041f        str     xzr, [x0, #8]
ffff800010013b84:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010013b88:       f85f8e5e        ldr     x30, [x18, #-8]!
ffff800010013b8c:       d50323bf        autiasp
ffff800010013b90:       d65f03c0        ret

If SCS protection is enabled, this function will return normally. If the function has __noscs attribute (scs disabled), it will crash due to 0 address access.

 Makefile                               |  20 ++-
 arch/Kconfig                           |  13 +-
 arch/arm64/Kconfig                     |   4 +-
 include/linux/compiler-gcc.h           |   4 +
 scripts/Makefile.gcc-plugins           |   7 +
 scripts/gcc-plugins/Kconfig            |   9 +
 scripts/gcc-plugins/arm64_scs_plugin.c | 299 +++++++++++++++++++++++++++++++++
 scripts/gcc-plugins/gcc-common.h       |   4 +
 8 files changed, 346 insertions(+), 14 deletions(-)
 create mode 100644 scripts/gcc-plugins/arm64_scs_plugin.c
Makefile +14 -6
index 61741e9..8039e61 100644
@@ -923,12 +923,6 @@ KBUILD_CFLAGS_KERNEL += -ffunction-sections -fdata-sections
 LDFLAGS_vmlinux += --gc-sections
 endif
 
-ifdef CONFIG_SHADOW_CALL_STACK
-CC_FLAGS_SCS	:= -fsanitize=shadow-call-stack
-KBUILD_CFLAGS	+= $(CC_FLAGS_SCS)
-export CC_FLAGS_SCS
-endif
-
 ifdef CONFIG_LTO_CLANG
 ifdef CONFIG_LTO_CLANG_THIN
 CC_FLAGS_LTO	:= -flto=thin -fsplit-lto-unit
@@ -1034,6 +1028,20 @@ include-$(CONFIG_GCC_PLUGINS)	+= scripts/Makefile.gcc-plugins
 
 include $(addprefix $(srctree)/, $(include-y))
 
+ifdef CONFIG_SHADOW_CALL_STACK
+
+ifdef CONFIG_CC_IS_CLANG
+CC_FLAGS_SCS	:= -fsanitize=shadow-call-stack
+endif
+
+ifdef CONFIG_CC_IS_GCC
+CC_FLAGS_SCS	:= $(ENABLE_SHADOW_CALL_STACK_PLUGIN)
+endif
+
+KBUILD_CFLAGS	+= $(CC_FLAGS_SCS)
+export CC_FLAGS_SCS
+endif
+
 # scripts/Makefile.gcc-plugins is intentionally included last.
 # Do not add $(call cc-option,...) below this line. When you build the kernel
 # from the clean source tree, the GCC plugins do not exist at this point.
arch/Kconfig +7 -6
index 98db634..1065cd0 100644
@@ -593,14 +593,15 @@ config ARCH_SUPPORTS_SHADOW_CALL_STACK
 	  switching.
 
 config SHADOW_CALL_STACK
-	bool "Clang Shadow Call Stack"
-	depends on CC_IS_CLANG && ARCH_SUPPORTS_SHADOW_CALL_STACK
+	bool "Shadow Call Stack"
+	depends on ARCH_SUPPORTS_SHADOW_CALL_STACK
 	depends on DYNAMIC_FTRACE_WITH_REGS || !FUNCTION_GRAPH_TRACER
 	help
-	  This option enables Clang's Shadow Call Stack, which uses a
-	  shadow stack to protect function return addresses from being
-	  overwritten by an attacker. More information can be found in
-	  Clang's documentation:
+	  This option enables Shadow Call Stack(supported as a compiler
+	  option in the case of clang, supported as a plugin in the case
+	  of gcc), which uses a shadow stack to protect function return
+	  addresses from being overwritten by an attacker.
+	  More information can be found in Clang's documentation:
 
 	    https://clang.llvm.org/docs/ShadowCallStack.html
 
arch/arm64/Kconfig +2 -2
index 62c3c1d..8aaa16e 100644
@@ -81,7 +81,7 @@ config ARM64
 	select ARCH_SUPPORTS_DEBUG_PAGEALLOC
 	select ARCH_SUPPORTS_HUGETLBFS
 	select ARCH_SUPPORTS_MEMORY_FAILURE
-	select ARCH_SUPPORTS_SHADOW_CALL_STACK if CC_HAVE_SHADOW_CALL_STACK
+	select ARCH_SUPPORTS_SHADOW_CALL_STACK if (CC_HAVE_SHADOW_CALL_STACK || GCC_PLUGIN_SHADOW_CALL_STACK)
 	select ARCH_SUPPORTS_LTO_CLANG if CPU_LITTLE_ENDIAN
 	select ARCH_SUPPORTS_LTO_CLANG_THIN
 	select ARCH_SUPPORTS_CFI_CLANG
@@ -1062,7 +1062,7 @@ config ARCH_HAS_FILTER_PGPROT
 
 # Supported by clang >= 7.0
 config CC_HAVE_SHADOW_CALL_STACK
-	def_bool $(cc-option, -fsanitize=shadow-call-stack -ffixed-x18)
+	def_bool CC_IS_CLANG || $(cc-option, -fsanitize=shadow-call-stack -ffixed-x18)
 
 config PARAVIRT
 	bool "Enable paravirtualization code"
include/linux/compiler-gcc.h +4 -0
index cb9217f..426c8e5 100644
@@ -50,6 +50,10 @@
 #define __latent_entropy __attribute__((latent_entropy))
 #endif
 
+#if defined(SHADOW_CALL_STACK_PLUGIN) && !defined(__CHECKER__)
+#define __noscs __attribute__((no_shadow_call_stack))
+#endif
+
 /*
  * calling noreturn functions, __builtin_unreachable() and __builtin_trap()
  * confuse the stack allocation in gcc, leading to overly large stack
scripts/Makefile.gcc-plugins +7 -0
index 952e468..b45bd8c 100644
@@ -46,6 +46,13 @@ ifdef CONFIG_GCC_PLUGIN_ARM_SSP_PER_TASK
 endif
 export DISABLE_ARM_SSP_PER_TASK_PLUGIN
 
+gcc-plugin-$(CONFIG_GCC_PLUGIN_SHADOW_CALL_STACK) += arm64_scs_plugin.so
+gcc-plugin-cflags-$(CONFIG_GCC_PLUGIN_SHADOW_CALL_STACK)	\
+		+= -DSHADOW_CALL_STACK_PLUGIN
+ifdef CONFIG_GCC_PLUGIN_SHADOW_CALL_STACK
+    ENABLE_SHADOW_CALL_STACK_PLUGIN += -fplugin-arg-arm64_scs_plugin-enable
+endif
+
 # All the plugin CFLAGS are collected here in case a build target needs to
 # filter them out of the KBUILD_CFLAGS.
 GCC_PLUGINS_CFLAGS := $(strip $(addprefix -fplugin=$(objtree)/scripts/gcc-plugins/, $(gcc-plugin-y)) $(gcc-plugin-cflags-y))
scripts/gcc-plugins/Kconfig +9 -0
index ab9eb4c..92f7f76 100644
@@ -19,6 +19,15 @@ menuconfig GCC_PLUGINS
 
 if GCC_PLUGINS
 
+config GCC_PLUGIN_SHADOW_CALL_STACK
+	bool "Plugin for ARM64 Shadow Call Stack"
+	depends on (!CC_HAVE_SHADOW_CALL_STACK) && ARM64
+	help
+	  This plugin is used to support kernel CONFIG_SHADOW_CALL_STACK
+	  compiled by gcc. Its principle is basically the same as that of
+	  CLANG.
+	  For more information, please refer to "config SHADOW_CALL_STACK"
+
 config GCC_PLUGIN_CYC_COMPLEXITY
 	bool "Compute the cyclomatic complexity of a function" if EXPERT
 	depends on !COMPILE_TEST	# too noisy
scripts/gcc-plugins/arm64_scs_plugin.c new file +299 -0

299 lines not on this page · raw message

scripts/gcc-plugins/gcc-common.h +4 -0
index 0c08761..7251b00 100644
@@ -27,6 +27,7 @@
 #include "except.h"
 #include "function.h"
 #include "toplev.h"
+#include "insn-codes.h"
 #if BUILDING_GCC_VERSION >= 5000
 #include "expr.h"
 #endif
@@ -535,6 +536,9 @@ static inline void ipa_remove_stmt_references(symtab_node *referring_node, gimpl
 }
 #endif
 
+/* RTL related */
+extern int recog(rtx, rtx_insn *, int *);
+
 #if BUILDING_GCC_VERSION < 6000
 #define get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, preversep, pvolatilep, keep_aligning)	\
 	get_inner_reference(exp, pbitsize, pbitpos, poffset, pmode, punsignedp, pvolatilep, keep_aligning)
Signature · 1 line
2.7.4
#1 lore raw
Quoted 40 lines · Dan Li

- This function can be used to test whether the shadow stack is effective:
//noinline void __noscs scs_test(void)
noinline void scs_test(void)

{
    register unsigned long *sp asm("sp");
    unsigned long * lr = sp + 1;

    asm volatile("":::"x30");
    *lr = 0;
}

when compiled with:
CONFIG_DYNAMIC_FTRACE_WITH_REGS=y
CONFIG_ARM64_PTR_AUTH_KERNEL=y
CONFIG_ARM64_BTI_KERNEL=y

ffff800010013b60 <scs_test>:

ffff800010013b60:       d503245f        bti     c
ffff800010013b64:       d503201f        nop
ffff800010013b68:       d503201f        nop
ffff800010013b6c:       d503233f        paciasp
ffff800010013b70:       f800865e        str     x30, [x18], #8
ffff800010013b74:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010013b78:       910003fd        mov     x29, sp
ffff800010013b7c:       910003e0        mov     x0, sp
ffff800010013b80:       f900041f        str     xzr, [x0, #8]
ffff800010013b84:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010013b88:       f85f8e5e        ldr     x30, [x18, #-8]!
ffff800010013b8c:       d50323bf        autiasp
ffff800010013b90:       d65f03c0        ret

If SCS protection is enabled, this function will return normally. If the function has __noscs attribute (scs disabled), it will crash due to 0 address access.

include/linux/compiler-gcc.h +0 -0
index cb9217f..426c8e5 100644

include/linux/compiler-gcc.h line 55

Quoted 6 lines
include/linux/compiler-gcc.h +3 -0
@@ -50,6 +50,10 @@
 #define __latent_entropy __attribute__((latent_entropy))
 #endif
 
+#if defined(SHADOW_CALL_STACK_PLUGIN) && !defined(__CHECKER__)
+#define __noscs __attribute__((no_shadow_call_stack))
+#endif

Cool this is a nice addition, and something I don't think that clang has. For any new feature, having a function attribute to disable it at the function granularity is nice, and plays better with LTO than -f group flags. Though that begs the question: what happens if a __noscs callee is inlined into a non-__noscs caller, or vice versa?

I noticed that __noscs isn't actually applied anywhere in the kernel, yet, at least in this series. Were there any places necessary that you've found thus far?

Overall, I'm happy with the patch and am ready to ack it, but I would like to see a link to to the upstream GCC feature request for SCS (and one created if it doesn't exist) cited explicitly in the commit message. I think that would be a good demonstration that this can or will be upstreamed into the compiler proper for the compiler vendors to maintain, rather than the kernel folks. The compiler vendors may have further feedback on the approach, such as my question above pertaining to inlining.

Signature · 2 lines
Thanks,
~Nick Desaulniers
author
#2 lore raw
Quoted 7 lines · Nick Desaulniers
Quoted 6 lines · Dan Li
include/linux/compiler-gcc.h +0 -0
@@ -50,6 +50,10 @@
  #define __latent_entropy __attribute__((latent_entropy))
  #endif
 
Quoted 9 lines

include/linux/compiler-gcc.h line 55

Quoted 3 lines
include/linux/compiler-gcc.h +3 -0
+#if defined(SHADOW_CALL_STACK_PLUGIN) && !defined(__CHECKER__)
+#define __noscs __attribute__((no_shadow_call_stack))
+#endif

Cool this is a nice addition, and something I don't think that clang has. For any new feature, having a function attribute to disable it at the function granularity is nice, and plays better with LTO than -f group flags. Though that begs the question: what happens if a __noscs callee is inlined into a non-__noscs caller, or vice versa?

Thanks Nick,

According to my understanding, all inline optimizations in gcc should happen before inserting scs insns (scs and paciasp/autiasp use the same insertion point). Therefore, the check for the __noscs attribute will also occur after all inlining is completed.

As in the following example:
- Since __noscs attribute is specified, scs_test1 does not insert scs insns
- Since normal functions scs_test2/3 uses x30, it needs to insert scs insns
- Since __noscs attribute is specified, scs_test4 after inlining does not need to insert scs insns

__always_inline __noscs void scs_test1(void)

{
    asm volatile("mov x1, x1\n\t":::"x30");
}

//scs insns inserted after function inline
void scs_test2(void)

{
    scs_test1();
}

__always_inline void scs_test3(void)

{
    asm volatile("mov x3, x3\n\t":::"x30");
}

//no scs insns inserted
__noscs void scs_test4(void)

{
    scs_test3();
}

ffff800010012900 <scs_test1>:

ffff800010012900:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010012904:       910003fd        mov     x29, sp
ffff800010012908:       aa0103e1        mov     x1, x1
ffff80001001290c:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012910:       d65f03c0        ret

ffff800010012914 <scs_test2>:

ffff800010012914:       f800865e        str     x30, [x18], #8
ffff800010012918:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff80001001291c:       910003fd        mov     x29, sp
ffff800010012920:       aa0103e1        mov     x1, x1
ffff800010012924:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012928:       f85f8e5e        ldr     x30, [x18, #-8]!
ffff80001001292c:       d65f03c0        ret

ffff800010012930 <scs_test3>:

ffff800010012930:       f800865e        str     x30, [x18], #8
ffff800010012934:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010012938:       910003fd        mov     x29, sp
ffff80001001293c:       aa0303e3        mov     x3, x3
ffff800010012940:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012944:       f85f8e5e        ldr     x30, [x18, #-8]!
ffff800010012948:       d65f03c0        ret
ffff80001001294c:       d503201f        nop

ffff800010012950 <scs_test4>:

ffff800010012950:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010012954:       910003fd        mov     x29, sp
ffff800010012958:       aa0303e3        mov     x3, x3
ffff80001001295c:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012960:       d65f03c0        ret
Quoted 3 lines

I noticed that __noscs isn't actually applied anywhere in the kernel, yet, at least in this series. Were there any places necessary that you've found thus far?

At present, I have not found a function that must use the __noscs attribute in the kernel. I have only used this attribute in test cases.

Quoted 9 lines

Overall, I'm happy with the patch and am ready to ack it, but I would like to see a link to to the upstream GCC feature request for SCS (and one created if it doesn't exist) cited explicitly in the commit message. I think that would be a good demonstration that this can or will be upstreamed into the compiler proper for the compiler vendors to maintain, rather than the kernel folks. The compiler vendors may have further feedback on the approach, such as my question above pertaining to inlining.

I have submitted a feature request to the gcc community, and waiting for a follow-up response.

Is it fine to add the following description in [PATCH V5]?

A similar feature request has also been sent to gcc.

#3 lore raw
Quoted 13 lines · Dan Li
Quoted 9 lines · Nick Desaulniers

Overall, I'm happy with the patch and am ready to ack it, but I would like to see a link to to the upstream GCC feature request for SCS (and one created if it doesn't exist) cited explicitly in the commit message. I think that would be a good demonstration that this can or will be upstreamed into the compiler proper for the compiler vendors to maintain, rather than the kernel folks. The compiler vendors may have further feedback on the approach, such as my question above pertaining to inlining.

Quoted 7 lines

I have submitted a feature request to the gcc community, and waiting for a follow-up response.

Is it fine to add the following description in [PATCH V5]?

A similar feature request has also been sent to gcc.

Yes, and you can include

too.

Signature · 2 lines
Thanks,
~Nick Desaulniers
#4 lore raw
In reply to Dan Li
Quoted 38 lines · Dan Li
Quoted 7 lines · Nick Desaulniers
Quoted 6 lines · Dan Li
include/linux/compiler-gcc.h +0 -0
@@ -50,6 +50,10 @@
  #define __latent_entropy __attribute__((latent_entropy))
  #endif
 
Quoted 9 lines

include/linux/compiler-gcc.h line 55

Quoted 3 lines
include/linux/compiler-gcc.h +3 -0
+#if defined(SHADOW_CALL_STACK_PLUGIN) && !defined(__CHECKER__)
+#define __noscs __attribute__((no_shadow_call_stack))
+#endif

Cool this is a nice addition, and something I don't think that clang has. For any new feature, having a function attribute to disable it at the function granularity is nice, and plays better with LTO than -f group flags. Though that begs the question: what happens if a __noscs callee is inlined into a non-__noscs caller, or vice versa?

Thanks Nick,

According to my understanding, all inline optimizations in gcc should happen before inserting scs insns (scs and paciasp/autiasp use the same insertion point). Therefore, the check for the __noscs attribute will also occur after all inlining is completed.

As in the following example:
- Since __noscs attribute is specified, scs_test1 does not insert scs insns
- Since normal functions scs_test2/3 uses x30, it needs to insert scs insns
- Since __noscs attribute is specified, scs_test4 after inlining does not need to insert scs insns

__always_inline __noscs void scs_test1(void)

{
     asm volatile("mov x1, x1\n\t":::"x30");
}
Quoted 5 lines

//scs insns inserted after function inline
void scs_test2(void)

{
     scs_test1();
}

That may be surprising to developers. Perhaps __always_inline on scs_test1 is distracting this test case, but I suspect it may not make a difference. This particular issue comes up time and again with stack protectors; ie. the callee is marked no stack protector, then gets inlined into a caller and suddenly gets a stack protector.

Quoted 49 lines

__always_inline void scs_test3(void)

{
     asm volatile("mov x3, x3\n\t":::"x30");
}

//no scs insns inserted
__noscs void scs_test4(void)

{
     scs_test3();
}

ffff800010012900 <scs_test1>:

ffff800010012900:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010012904:       910003fd        mov     x29, sp
ffff800010012908:       aa0103e1        mov     x1, x1
ffff80001001290c:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012910:       d65f03c0        ret

ffff800010012914 <scs_test2>:

ffff800010012914:       f800865e        str     x30, [x18], #8
ffff800010012918:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff80001001291c:       910003fd        mov     x29, sp
ffff800010012920:       aa0103e1        mov     x1, x1
ffff800010012924:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012928:       f85f8e5e        ldr     x30, [x18, #-8]!
ffff80001001292c:       d65f03c0        ret

ffff800010012930 <scs_test3>:

ffff800010012930:       f800865e        str     x30, [x18], #8
ffff800010012934:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010012938:       910003fd        mov     x29, sp
ffff80001001293c:       aa0303e3        mov     x3, x3
ffff800010012940:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012944:       f85f8e5e        ldr     x30, [x18, #-8]!
ffff800010012948:       d65f03c0        ret
ffff80001001294c:       d503201f        nop

ffff800010012950 <scs_test4>:

ffff800010012950:       a9bf7bfd        stp     x29, x30, [sp, #-16]!
ffff800010012954:       910003fd        mov     x29, sp
ffff800010012958:       aa0303e3        mov     x3, x3
ffff80001001295c:       a8c17bfd        ldp     x29, x30, [sp], #16
ffff800010012960:       d65f03c0        ret
Quoted 3 lines

I noticed that __noscs isn't actually applied anywhere in the kernel, yet, at least in this series. Were there any places necessary that you've found thus far?

At present, I have not found a function that must use the __noscs attribute in the kernel. I have only used this attribute in test cases.

Signature · 2 lines
Thanks,
~Nick Desaulniers
author
#5 lore raw
Quoted 45 lines · Nick Desaulniers
Quoted 38 lines · Dan Li
Quoted 7 lines · Nick Desaulniers
Quoted 6 lines · Dan Li
include/linux/compiler-gcc.h +0 -0
@@ -50,6 +50,10 @@
   #define __latent_entropy __attribute__((latent_entropy))
   #endif
 
Quoted 9 lines

include/linux/compiler-gcc.h line 55

Quoted 3 lines
include/linux/compiler-gcc.h +3 -0
+#if defined(SHADOW_CALL_STACK_PLUGIN) && !defined(__CHECKER__)
+#define __noscs __attribute__((no_shadow_call_stack))
+#endif

Cool this is a nice addition, and something I don't think that clang has. For any new feature, having a function attribute to disable it at the function granularity is nice, and plays better with LTO than -f group flags. Though that begs the question: what happens if a __noscs callee is inlined into a non-__noscs caller, or vice versa?

Thanks Nick,

According to my understanding, all inline optimizations in gcc should happen before inserting scs insns (scs and paciasp/autiasp use the same insertion point). Therefore, the check for the __noscs attribute will also occur after all inlining is completed.

As in the following example:
- Since __noscs attribute is specified, scs_test1 does not insert scs insns
- Since normal functions scs_test2/3 uses x30, it needs to insert scs insns
- Since __noscs attribute is specified, scs_test4 after inlining does not need to insert scs insns

__always_inline __noscs void scs_test1(void)

{
      asm volatile("mov x1, x1\n\t":::"x30");
}
Quoted 5 lines

//scs insns inserted after function inline
void scs_test2(void)

{
      scs_test1();
}
Quoted 6 lines

That may be surprising to developers. Perhaps __always_inline on scs_test1 is distracting this test case, but I suspect it may not make a difference. This particular issue comes up time and again with stack protectors; ie. the callee is marked no stack protector, then gets inlined into a caller and suddenly gets a stack protector.

Yes.

I haven’t noticed this issue. I just took a quick look at the stack canary code, and found that the instructions are generated in the RTL stage like scs/pac (AST => GIMPLE => RTL => asm output), and the inlining should have been completed before this.

Generally, instructions that are sensitive to assembly order can only be inserted during RTL, otherwise their order is difficult to guarantee. I think this may be the reason why the similar problem always appears.