https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128271
Bug ID: 128271
Summary: [x86] CET shadow-stack clrssbsy/wrss* patterns omit
FLAGS_REG and memory clobbers
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: x86_64-*-*, i?86-*-*
Summary
-------
In `gcc/config/i386/i386.md:30345-30391`, the x86 CET shadow-stack RTL patterns
omit processor flag and memory clobbers required by the Intel SDM:
1. `define_insn "clrssbsy"` (line 30384) omits `(clobber (reg:CC FLAGS_REG))`.
Per the Intel SDM, `CLRSSBSY m64` modifies `CF` and unconditionally clears
`ZF = 0`, `OF = 0`, `AF = 0`, `PF = 0`, and `SF = 0`. Because `FLAGS_REG` is
not clobbered in RTL, `pass_compare_elim_after_reload` deletes an explicit
`test`/`cmp` after `_clrssbsy(tok)` and reuses condition codes set before
`clrssbsy`. Since `clrssbsy` clears `ZF = 0` in hardware, a subsequent
`je`/`jz` branch is never taken (wrong-code).
2. All five shadow-stack write/token patterns (`saveprevssp`, `rstorssp`,
`@wrss<mode>`, `@wruss<mode>`, `clrssbsy`) omit `(clobber (mem:BLK
(scratch)))`.
In `gcc/cse.cc:6181-6216` (`invalidate_from_clobbers`), `UNSPEC_VOLATILE`
does not call `invalidate_memory()` without a memory clobber, so `cse1`
folds a post-`_wrssq` verification load `*ss_slot == val` to stale pre-write
flags.
Reproducer
----------
$ cat trigger.c
#include <immintrin.h>
#include <stdint.h>
void on_zero(void);
void test_clrssbsy_cmpelim(long *counter, void *tok) {
long v = *counter - 1;
*counter = v;
_clrssbsy(tok);
if (v == 0)
on_zero();
}
int verify_wrssq(uint64_t *ss_slot, uint64_t val) {
if (*ss_slot == val)
return 1;
_wrssq(val, ss_slot);
return (*ss_slot == val);
}
Compile flag: gcc -O2 -mshstk -S trigger.c
$ cat trigger.s
test_clrssbsy_cmpelim:
subq $1, (%rdi)
clrssbsy (%rsi) # hardware clears ZF=0!
je .L4 # compare-elim deleted testq; je is now
dead!
ret
.L4:
jmp on_zero
verify_wrssq:
movl $1, %eax
cmpq %rsi, (%rdi)
je .L5
wrssq %rsi, (%rdi)
sete %al # cse1 deleted post-wrssq reload;
always returns 0!
movzbl %al, %eax
.L5:
ret
Suggested Fix
-------------
In `gcc/config/i386/i386.md:30345-30391`:
1. Add `(clobber (reg:CC FLAGS_REG))` to `define_insn "clrssbsy"`.
2. Add `(clobber (mem:BLK (scratch)))` to `saveprevssp`, `rstorssp`,
`@wrss<mode>`, `@wruss<mode>`, and `clrssbsy`.