https://gcc.gnu.org/bugzilla/show_bug.cgi?id=128036
Bug ID: 128036
Summary: [LoongArch] -fstack-clash-protection leaves residual
stack allocation unprobed
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: jianhao.xu at seu dot edu.cn
Target Milestone: ---
Summary
-------
In `gcc/config/loongarch/loongarch.cc:1229-1245`,
`loongarch_emit_probe_stack_range`
completely omits emitting the probe store for Step-4 residual stack
allocations:
/* Step 4: probe at FIRST + SIZE if we cannot assert at compile-time
that SIZE is equal to ROUNDED_SIZE. */
if (size != rounded_size)
{
if (size - rounded_size >= 2048)
{
emit_move_insn (r14, GEN_INT (size - rounded_size));
emit_insn (gen_rtx_SET (stack_pointer_rtx,
gen_rtx_MINUS (Pmode, stack_pointer_rtx,
r14)));
}
else
emit_insn (gen_rtx_SET (stack_pointer_rtx,
gen_rtx_PLUS (Pmode, stack_pointer_rtx, GEN_INT
(rounded_size - size))));
}
The comment explicitly states "probe at FIRST + SIZE", but the implementation
only adjusts the stack pointer `$r3` and never emits any probe instruction
(`emit_stack_probe`).
Under `-fstack-clash-protection` (default 4 KiB guard), if a function allocates
a frame with a large residual (e.g. 8160 bytes), the residual lowers SP without
probing. When calling a callee that allocates a sub-guard frame (which drops SP
without probing under `NO_PROBE_SMALL_FRAME`), up to 8144 bytes (> 1.99 guard
pages)
are traversed without a single stack touch. Because LoongArch `bl`/`jirl`
stores
the return address in register `$r1` rather than memory, an entire 4 KiB guard
page
can be skipped without triggering a fault.
Reproducer
----------
$ cat trigger.c
__attribute__((noinline)) void leaf (void) {
volatile char b[4080];
b[0] = 1;
}
void caller (void) {
volatile char pad[8160];
leaf ();
pad[0] = 1;
}
Compile flag: loongarch64-linux-gnu-gcc -O2 -fstack-clash-protection -S
trigger.c
$ cat trigger.s
caller:
addi.d $r3,$r3,-16
lu12i.w $r14,1
st.d $r1,$r3,8
sub.d $r3,$r3,$r14
st.d $r0,$r3,0 # probe at sp0-4112
ori $r14,$r0,4064
sub.d $r3,$r3,$r14 # sp -= 4064 (NO PROBE for residual)
la.local $r12,leaf
jirl $r1,$r12,0
leaf:
ori $r14,$r0,4080
sub.d $r3,$r3,$r14 # sp -= 4080 (NO PROBE, small frame)
addi.w $r13,$r0,1
st.b $r13,$r3,0 # first touch at sp0-12256
Key Observation:
- Deepest access in caller: `st.d $r0,$r3,0` at sp0-4112.
- Next memory access in leaf: `st.b $r13,$r3,0` at sp0-12256.
- Untouched span = 8144 bytes, jumping over an entire 4096-byte guard page.