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, ...).