https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128300

            Bug ID: 128300
           Summary: ipa-strub: STRUB_INLINABLE emits unscrubbed
                    out-of-line body when address-taken and misses
                    late-inlined VLA watermark
           Product: gcc
           Version: 16.2.0
            Status: UNCONFIRMED
          Severity: normal
          Priority: P3
         Component: ipa
          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: *-*-*

Summary
-------
In `gcc/ipa-strub.cc:1206-1219, 2781-2793`, functions annotated with both
`__attribute__((strub("internal")))` and `always_inline` are classified as
`STRUB_INLINABLE` instead of `STRUB_WRAPPED`, which causes two stack-scrubbing
bypasses:

1. Unscrubbed out-of-line body when address-taken:
   `pass_ipa_strub::execute` (`ipa-strub.cc:2792`) skips wrapping for
   `STRUB_INLINABLE`. If the function has external linkage or its address is
   taken (`&wipe_inline_fn`), GCC still emits a standalone out-of-line function
   body, but that body contains zero `__strub_enter`, `__strub_update`, or
   `__strub_leave` calls. Any indirect call leaves its stack frame completely
   unscrubbed.
2. Late-inlined VLA escapes watermark:
   When a `STRUB_INLINABLE` helper containing a VLA or `__builtin_alloca` is
   called via a local function pointer that `pass_ipa_inline` (running after
   `pass_ipa_strub`) devirtualizes and inlines into a `strub("internal")`
   caller, the caller's `__strub_update` watermark update remains before the
   inlined `subq %rax, %rsp` VLA allocation, leaving the entire VLA below the
   scrub watermark.

Reproducer
----------
$ cat trigger.c
void use_buf(volatile char *, int);

__attribute__((always_inline, strub("internal")))
int wipe_inline_fn(int n) {
    volatile char secret[32];
    secret[0] = (char)n;
    use_buf(secret, 32);
    return secret[0];
}

int (*export_ptr(void))(int) {
    return &wipe_inline_fn;
}

Compile flag: gcc -O2 -S trigger.c

$ cat trigger.s
wipe_inline_fn:
        subq    $40, %rsp               # allocates 32-byte secret buffer
        movl    $32, %esi
        movb    %dil, (%rsp)
        movq    %rsp, %rdi
        call    use_buf
        movsbl  (%rsp), %eax
        addq    $40, %rsp
        ret                             # ZERO __strub_enter/update/leave!

Suggested Fix
-------------
In `gcc/ipa-strub.cc:1206-1219`, reject or wrap `STRUB_INLINABLE` functions
whose address is taken / that remain externally callable out-of-line, and emit
a `__builtin___strub_update` call when inlining a `STRUB_INLINABLE` callee that
allocates dynamic stack space.

Reply via email to