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

            Bug ID: 128576
           Summary: strub_inlinable_to_p allows inlining strub("callable")
                    functions into strub contexts without re-verifying
                    their callees, bypassing -fstrub=strict to call
                    strub("disabled") functions
           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: *-*-*

Reproducer
----------
$ cat trigger.c
volatile int sink;

void __attribute__((noinline, strub("disabled"))) bad(void) { sink = 1; }

static inline void __attribute__((strub("callable"))) bridge(void) {
    bad();
}

void __attribute__((strub("internal"))) s(void) {
    bridge();
}

Compile command / flags:
podman exec defuzz-toolchain-daemon /opt/gcc16/bin/x86_64-linux-gnu-gcc -O2
-fstrub=strict -Wall -Wextra -S -o - /tmp/t27_test.c

Observed output (GCC 16.2.0):
s.strub.0:
        leaq    -128(%rsp), %rax
        cmpq    (%rdi), %rax
        jnb     .L4
        movq    %rax, (%rdi)
.L4:
        jmp     bad
(Zero error diagnostic under -fstrub=strict; the strubbed body s.strub.0
directly tail-calls bad() which is strub("disabled"))

Expected behavior:
Inlining a STRUB_CALLABLE (or STRUB_DISABLED) function into a strub context
must be disallowed whenever the callee contains any call to a function that is
not itself strub_callable_from_p the caller's strub mode (or verify_strub must
re-run after inlining).

Root cause
----------
Source location: gcc/ipa-strub.cc:1484-1505 (strub_inlinable_to_p) and
1695-1725 (verify_strub)

In gcc/ipa-strub.cc, verify_strub runs during pass_ipa_strub_mode (before IPA
inlining) and checks only edges whose caller is a strub context. When 's' is
strub("internal"), 'bridge' is static inline strub("callable"), and 'bad' is
strub("disabled"), verify_strub checks s -> bridge (allowed, because bridge is
callable) and skips checking bridge -> bad (because bridge is NOT a strub
context, and non-strub functions are allowed to call disabled functions).
Later, during IPA inlining, can_inline_edge_p calls strub_inlinable_to_p
(callee=bridge, caller=s) (ipa-strub.cc:1484-1505). At lines 1498-1504, case
STRUB_CALLABLE unconditionally returns true with the comment: 'When we consider
inlining, we've already verified callability, so we can even inline callable
and then disabled into a strub context.' Once bridge is inlined into s, the
strub context s directly calls bad (strub("disabled")) with zero diagnostic!

Impact:
Complete bypass of -fstrub=strict call-graph isolation via any inline
strub("callable") helper function.

Suggested Fix
-------------
In gcc/ipa-strub.cc:1498-1504 (strub_inlinable_to_p), when caller is a strub
context and callee is STRUB_CALLABLE or STRUB_DISABLED, iterate over
callee->callees and callee->indirect_calls and return false if any target would
violate strub_callable_from_p (caller_mode, ...).

Reply via email to