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`.

Reply via email to