https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128284
Bug ID: 128284
Summary: [AArch64] Guarded Control Stack aarch64_gcsss1/gcsss2
RTL patterns miss memory clobber
Product: gcc
Version: 16.2.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: target
Assignee: unassigned at gcc dot gnu.org
Reporter: 220245569 at seu dot edu.cn
CC: 220255623 at seu dot edu.cn, jianhao.xu at seu dot edu.cn
Target Milestone: ---
Target: aarch64-*-*
Summary
-------
In `gcc/config/aarch64/aarch64.md:8926-8937`, the Armv9.4-A Guarded Control
Stack switch patterns `aarch64_gcsss1` and `aarch64_gcsss2` (backing the ACLE
`__gcsss` intrinsic) are defined as `UNSPEC_VOLATILE` without a memory clobber:
(define_insn "aarch64_gcsss1"
[(unspec_volatile [(match_operand:DI 0 "register_operand" "r")]
UNSPECV_GCSSS1)]
"TARGET_GCS"
"sys\t#3, c7, c7, #2, %0 // gcsss1"
)
Architecturally, `GCSSS1` validates and consumes the cap token at
`*new_gcs_cap`
and `GCSSS2` writes an in-progress/switched token into GCS memory (which is
readable by normal load instructions). Because `gcc/cse.cc:6181-6216` does not
invalidate memory on `UNSPEC_VOLATILE` unless `(clobber (mem:BLK (scratch)))`
is present, RTL `cse1` deletes any post-`__gcsss` reload from `*new_gcs_cap`
and
reuses the pre-switch value.
Reproducer
----------
$ cat trigger.c
#include <arm_acle.h>
#include <stdint.h>
uint64_t check_gcs_switch(uint64_t *new_gcs_cap) {
uint64_t cap_before = *new_gcs_cap;
void *old_gcs = __gcsss((void *)new_gcs_cap);
(void)old_gcs;
return cap_before ^ *new_gcs_cap;
}
Compile flag: aarch64-linux-gnu-gcc -ffreestanding -O2 -march=armv9.4-a+gcs -S
trigger.c
$ cat trigger.s
check_gcs_switch:
sys #3, c7, c7, #2, x0 // gcsss1
mov x0, 0
sysl x0, #3, c7, c7, #3 // gcsss2
mov x0, 0 # cse1 folded cap_before ^ *new_gcs_cap
to 0!
ret
Suggested Fix
-------------
Add `(clobber (mem:BLK (scratch)))` to `define_insn "aarch64_gcsss1"` and
`define_insn "aarch64_gcsss2"` in `gcc/config/aarch64/aarch64.md:8926-8937`.