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.

Reply via email to