Threat Model and Linux CET Prerequisites
SFOP does not make arbitrary, non-vulnerable programs exploitable via normal crashes. Instead, it operates under a specific post-corruption threat model:
-
Initial Memory Corruption Required: The target application must contain an exploitable memory corruption vulnerability (such as a stack buffer overflow or a writable function/vtable pointer) that allows an attacker to hijack a control-flow edge and invoke signal registration routines (
signalorsigaction). -
Runtime CET Environment: Compiling a binary with
-fcf-protection=fullinserts CET metadata andENDBR64instructions, but runtime protection requires simultaneous hardware, kernel, and C library support. On 64-bit Linux, mainline support centers on userspace Shadow Stacks (CONFIG_X86_USER_SHADOW_STACK, supported since Linux 6.6 and glibc 2.39, often requiring explicitGLIBC_TUNABLESactivation) alongside hardware support on Intel Tiger Lake (11th Gen) or newer processors.
Background: Linux Shadow Stack Handling During Signals
When a process running with an active hardware shadow stack receives a signal, the Linux kernel saves the interrupted execution state so the program can resume after the user-space signal handler finishes:
-
User-Space sigframe Construction: The kernel writes a
sigframestructure onto the normal user-space stack containing the saved CPU registers (ucontext) and a restorer return address (which libc normally sets to__restore_rt). - Shadow Stack Token Insertion: To prevent classic SROP attacks from forging signal frames to pivot the Shadow Stack Pointer (SSP), the kernel pushes the pre-signal SSP onto the hardware shadow stack in a special token format with bit 63 (the Most Significant Bit, MSB) set to 1, followed by the restorer address.
-
Handler Return and sys_rt_sigreturn: When the signal handler executes
ret, the CPU compares the return address on the normal stack against the restorer address on the shadow stack. Execution then enters__restore_rt, which invokes thesys_rt_sigreturnsystem call. Duringsys_rt_sigreturn, the kernel verifies that the shadow stack token has MSB == 1 before restoring the pre-signal SSP.
Normal Execution (Userspace CET Active)
│
▼ (Signal Delivered to Process)
┌──────────────────────────────────────────────────────────────────────┐
│ Linux Kernel Signal Setup │
│ 1. Writes ucontext + restorer pointer to User Stack (sigframe) │
│ 2. Pushes (Old SSP | Bit 63 = 1) + restorer to Hardware Shadow Stack │
└──────────────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ User-Space Signal Handler │
│ - Executes handler code │
│ - Executes 'ret' -> CPU checks User Stack vs. Shadow Stack restorer │
└──────────────────────────────────┬───────────────────────────────────┘
│
▼
┌──────────────────────────────────────────────────────────────────────┐
│ Restorer (__restore_rt) -> Invokes sys_rt_sigreturn │
│ - Kernel checks Bit 63 == 1 token on Shadow Stack │
└──────────────────────────────────────────────────────────────────────┘
Core Weaknesses in Signal-CET Interactions
The researchers combined one known weakness with twelve newly identified flaws in the Linux signal subsystem to construct the SFOP attack primitives:
1. Lack of Kernel IBT Verification on Handler Entry (Known Weakness, §3.3.1)
When the kernel transfers control to a registered signal handler, it does not verify whether the handler's target address begins with a valid ENDBR64 instruction. If an attacker can invoke signal or sigaction with an arbitrary handler address, they can jump into the middle of functions, bypassing forward-edge IBT landing-pad restrictions.
2. Late Kernel Safety Checks in sys_rt_sigreturn (§3.3.2)
When an attacker invokes sys_rt_sigreturn with a counterfeit sigframe, the kernel checks the shadow stack token's MSB to block SROP. However, the authors found that the kernel performs this MSB check after it has already restored the general-purpose CPU registers from the user-space sigframe.
When the MSB check fails, the kernel attempts to terminate the process by delivering a SIGSEGV signal. If the attacker has already registered a custom handler for SIGSEGV (and bypassed signal masking via signal nesting, §3.3.11), the process catches the SIGSEGV and enters the handler with the general-purpose registers holding the attacker-supplied values from the forged sigframe.
[ Attacker Forges sigframe on User Stack ]
│
▼
[ Invokes sys_rt_sigreturn ]
│
├──> 1. Kernel restores CPU registers from forged sigframe
├──> 2. Kernel checks Shadow Stack MSB token -> FAILS
└──> 3. Kernel delivers catchable SIGSEGV to process
│
▼
[ Registered SIGSEGV Handler Catches Fault ]
* Process survives termination with forged register values intact
* Attacker chains execution into subsequent "Faulty Gadgets"
3. Signal Restorer Hijacking and Shadow Stack Injection (sigskip, §3.3.3–§3.3.5)
Normally, libc's sigaction wrapper overwrites any user-supplied sa_restorer pointer with __restore_rt. However, an attacker can register a custom restorer either by invoking the raw sys_rt_sigaction syscall directly or by jumping past the prologue instructions in libc's sigaction that overwrite the restorer field (a mid-function entry point dubbed sigskip, reachable because the kernel omits IBT checks on signal handlers).
Because the kernel pushes the registered restorer address onto the hardware shadow stack whenever a signal is delivered, registering a custom restorer lets an attacker inject arbitrary values onto the read-only shadow stack, which can then be used to validate return addresses or manipulate the SSP in backward-edge SFOP (bSFOP).
4. Faulty Gadgets, Early Kill, and Surviving CET Violations (§3.3.7–§3.3.12)
The authors define faulty gadgets as legitimate functions containing instructions that deterministically raise a segmentation fault when passed corrupted pointers. In particular, they identify an Early Kill faulty gadget inside libc's sigaction coupled with a user-kernel data collision on the oldact parameter (§3.3.8–§3.3.10).
Furthermore, whenever a hardware CET shadow stack violation occurs, the kernel responds by sending a catchable SIGSEGV (§3.3.12). By catching these recurring SIGSEGV signals, SFOP uses faults as an implicit loop dispatcher to chain arbitrary function calls. Note that because the kernel overwrites rdi, rsi, and rdx with signal arguments (signum, siginfo_t *, ucontext_t *) on every signal delivery, the attacker relies on preserved registers (such as rbp) or user-kernel memory collisions to control arguments across the chain.
Proof-of-Concept Evaluations: vulnerable-nginx and vulnerable-ladybird
The authors developed two variants of the technique—fSFOP (forward-edge SFOP) and bSFOP (backward-edge SFOP)—and released six standalone proof-of-concept exploits alongside two application demonstrations in their artifact repository (signal-sfop/sfop).
Rather than exploiting zero-day flaws in current software releases, the application evaluations tested modified versions of real-world codebases containing known historical vulnerabilities, compiled and executed on bare-metal CET-capable hardware (tested on an Intel Core i3-14100 running Ubuntu 24.04 with Linux kernel 6.14 and glibc 2.39):
| Artifact Target | Base Codebase & Reintroduced Bug | Demonstrated Variant | Exploit Mechanism |
|---|---|---|---|
vulnerable-nginx |
Modified Nginx with CVE-2013-2028 (stack buffer overflow) | fSFOP | Uses a custom signal-registration chain and the sigaction Early Kill faulty gadget to achieve code execution with CET active. |
vulnerable-ladybird |
Modified SerenityOS / Ladybird browser with CVE-2021-4327 | fSFOP | Uses a custom signal-registration chain and Early Kill dispatcher to hijack control flow under active CET enforcement. |
Proposed Mitigations and Disclosure Status
The researchers initiated a coordinated disclosure process with the Linux kernel security team and proposed a series of kernel-level mitigations designed to close the identified signal-handling gaps:
-
Preventing Late Register Restoration Leakage: Ensuring that failed shadow stack token checks during
sys_rt_sigreturndo not leave attacker-controlled register contexts accessible to catchable user-space handlers. -
Eliminating User-Kernel Collisions in sigaction: Fixing the partial memory overwrite behavior in
sigactionso the Early Kill faulty gadget can no longer be paired with a user-kernel data collision. - Restricting Custom Restorers and Enforcing Handler Checks: Validating restorer pointers and enforcing landing-pad checks on signal delivery.
According to the published paper, at the time of publication the Linux kernel security team had acknowledged the findings and was reviewing the authors' proposed patch series. In the authors' evaluation of their patch set, bSFOP is fully patched, while fSFOP is partially mitigated—primarily because closing the user-kernel collision neutralizes the sigaction faulty gadget used to bootstrap argument control, though theoretical forward-edge signal chaining remains a broader design challenge whenever an attacker can repeatedly invoke sigaction via corrupted function pointers.