Zicfilp: RISC-V CFI Landing Pad Extension
- Zicfilp is a RISC-V landing-pad extension for forward-edge CFI that requires every indirect jump to be immediately followed by a matching landing pad instruction.
- It integrates a dedicated hardware Landing Pad Unit in the CVA6 core’s commit stage to enforce runtime label matching and block code-reuse attacks like JOP and COP.
- Empirical evaluations reveal minimal performance overhead (<1%) and modest area increases, illustrating its practical efficiency and scalability in in-order pipelines.
Zicfilp is the RISC-V “landing pad” extension for forward-edge Control-Flow Integrity (CFI). In the CVA6-CFI implementation, it is presented as a mechanism that prevents an attacker from diverting an indirect jump or call to an arbitrary location by requiring every indirect transfer to be immediately followed by a special landing-pad instruction whose static label exactly matches a runtime value. The design is integrated into the open-source CVA6 core through a small hardware Landing Pad Unit (LPU) coupled to the commit stage, and is evaluated together with the companion shadow-stack extension Zicfiss in “CVA6-CFI: A First Glance at RISC-V Control-Flow Integrity Extensions” (Manoni et al., 4 Feb 2026).
1. Scope, objective, and threat model
Zicfilp is defined around forward-edge protection only. The protected events are indirect jumps and calls, including jalr, C++ virtual calls, and function pointer calls. The stated guarantee is that after any indirect transfer, the very next retired instruction must be a matching landing pad; otherwise, an exception is raised. Within that scope, the extension is intended to stop code-reuse attacks such as JOP and COP by forbidding out-of-domain control-transfer targets.
The threat model is explicitly bounded. It assumes an adversary who can corrupt memory and registers via a memory-unsafe language, but cannot break the hardware label-matching check or tamper with the dedicated CSR that enables Zicfilp. This delimitation is significant because it separates architectural enforcement from broader software compromise: the mechanism is not described as a complete defense against arbitrary compromise, but as a hardware-enforced constraint on forward-edge control flow.
A common misconception is to treat Zicfilp as synonymous with general-purpose CFI. The design description does not support that interpretation. It protects only forward-edge transfers, while backward-edge protection is assigned to the separate shadow-stack extension Zicfiss. This suggests that the intended security model is compositional rather than monolithic.
2. Landing-pad primitives and execution semantics
The extension uses two principal primitives. The first is lpad imm, described as a pseudo-instruction encoded as an AUIPC x0, offset whose offset field encodes a 20-bit label. It has no architectural side-effects except that its label must match the previously recorded label, after which the core returns to normal execution. The second is setl plt, label, described operationally as compiler-emitted setup before an indirect jump: either a CSR write or a simple addi x7, x0, label loads the expected label into architectural register x7. In this formulation, x7 always holds the current landing-pad label.
The abstract state machine is given as follows:
Here, WaitLP denotes the state after an indirect transfer has retired and before the required landing pad has retired. If the next non-speculative retire is an lpad whose embedded label exactly equals the value in x7, execution resumes. Otherwise, a CFI exception is signaled (Manoni et al., 4 Feb 2026).
This semantics is deliberately narrow and strict. The landing pad is not a general annotation on valid targets; it is a retirement-time obligation on the immediately following retired instruction. A plausible implication is that the extension is optimized for enforceability in simple pipelines, because the enforcement point is anchored to retirement rather than to speculative execution.
3. CVA6-CFI microarchitecture
In CVA6-CFI, Zicfilp is realized by a Landing Pad Unit placed just after the scoreboard and just before the commit ports. In CVA6’s six-stage in-order pipeline, the LPU observes instructions at the point they are about to retire. The described interface includes, for each retire port, inputs (instr_type, instr_fields, speculative_bit, reg_write_port), state variables cur_label and fp_state, and outputs that either allow retirement or raise an exception.
The state variables are defined concretely. cur_label is a 32-bit register updated on the last write to x7. fp_state ranges over {Idle, WaitLP}. The essential logic is:
- On any retirement that writes to
x7,cur_label ← write_data. - If an indirect jump retires,
fp_state ← WaitLPand retirement is allowed. - If an
lpadwith embedded label retires, then:- if
fp_state = WaitLPand \ell \ne cur_label`, a CFI exception is raised; - if
fp_state = Idle, thelpadis treated as a NOP and retirement is allowed.
- if
- If any other instruction retires while
fp_state = WaitLP, a CFI exception is raised.
The paper gives a pseudo-Verilog fragment that captures this behavior:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
always_comb begin
exception = 0;
next_fp_state = fp_state;
case (instr_type)
INDIRECT_JUMP: next_fp_state = WAIT_LP;
LPAD: begin
if (fp_state == WAIT_LP && instr_label != cur_label)
exception = 1;
else
next_fp_state = IDLE;
end
default: if (fp_state == WAIT_LP) exception = 1;
endcase
end |
The implementation also addresses multiple commit ports. One LPU is chained per port, and the design carries forward any “still waiting for landing pad” flag so that if two instructions retire in the same cycle, such as an indirect jump and its landing pad, the second port’s LPU still enforces the check. This point is architecturally important because the correctness criterion is expressed over retired instructions, not merely decoded or executed instructions.
4. Instrumentation model and code generation
The instrumentation strategy is compiler-facing. Before every indirect jump, the compiler is required to insert code that loads the expected landing-pad label into x7, and the control-transfer target must begin with an lpad carrying the matching embedded label. The canonical sequence is:
$\begin{array}{l} \texttt{\# Before instrumentation:}\ \quad {\tt jalr}\;\tt{ra,\,0(t0)} \[4pt] \texttt{\# Zicfilp-instrumented:}\ \quad {\tt li}\;x7,\;\texttt{LP\_ID\_42}\quad\# load label into x7 \ \quad {\tt jalr}\;\texttt{ra,\,0(t0)}\quad\# indirect call \ \quad {\tt lpad}\;\# encoded as `auipc x0, offset42` \ \quad \vdots\quad\# target function body must begin here \end{array}$
The compiler-friendly pseudo-assembly is likewise explicit:
The operational consequence is that x7 is reserved for CFI labels. The paper explicitly notes that this compiler requirement can create register-pressure issues (Manoni et al., 4 Feb 2026). It also requires a precise compiler pass that inserts li x7,label immediately before every indirect jump. A plausible implication is that deployment quality depends not only on the LPU hardware but also on the precision and completeness of toolchain instrumentation.
5. Configuration and deployment choices
The design exposes two main configuration axes. First, a CSR bit in the machine, supervisor, or user CSR configuration registers globally enables or disables landing-pad checking per privilege level. When disabled, all of the landing-pad logic is removed from the retirement decision, and instructions simply retire normally. The paper states that this trades away security, meaning CFI is disabled, in exchange for zero performance overhead.
Second, the number of commit ports can be selected at design time. If the core retires only one instruction per cycle, a single LPU suffices and area is further reduced. If the design is multi-issue, multiple LPUs are required. This is less a policy knob than an implementation parameter, but it directly links the cost of Zicfilp enforcement to the retirement structure of the target core.
These options clarify that Zicfilp is not specified as a single immutable block. In CVA6-CFI it is an independently configurable hardware unit for forward-edge protection, designed so that security coverage, area, and pipeline complexity can be traded against one another in a controlled way.
6. Empirical results in CVA6-CFI
The paper evaluates Zicfilp together with Zicfiss and then extracts the figures most attributable to Zicfilp. For area, under 22 nm FDX at 800 MHz worst-case corner, the commit stage area increases from 182 μm² to 372 μm² (+104%), with roughly half of that doubling attributed to the two LPUs. At the whole-pipeline level, area grows from 96 382 μm² to 97 358 μm² (+1.0%), and Zicfilp’s share is stated to be on the order of +0.4% total (Manoni et al., 4 Feb 2026).
For performance, the MiBench automotive subset is used. There, lpad instructions account for \<0.5% of dynamic instruction count. Because each lpad costs exactly one cycle check in the commit stage and requires no extra memory access, the forward-edge penalty is described as negligible and often below measurement noise compared to baseline. The worst-case end-to-end slowdown including both Zicfilp and the shadow stack is 15.6%, while the purely Zicfilp-related overhead is typically \<1%.
These figures should be interpreted carefully. The reported worst-case slowdown is not a direct measure of Zicfilp in isolation; it is the combined overhead of forward-edge and backward-edge CFI. By contrast, the article’s characterization of Zicfilp alone is that the landing-pad checks themselves impose essentially no dedicated performance overhead beyond a very small dynamic-instruction contribution from lpad.
7. Limitations, edge cases, and extension challenges
Several limitations and open questions are explicitly identified. Chaining LPUs for multi-port retirement increases critical-path delay in the commit stage, although in CVA6 this did not reduce clock frequency. Designers of higher-frequency or out-of-order cores are therefore expected to revisit placement or pipeline balancing.
Interrupts and exceptions introduce a state-management problem. If an interrupt or exception occurs between the indirect jump and its landing pad, software or hardware must ensure that the WaitLP state is cleared, for example on context switch, or that the handler sequence itself includes a dummy lpad to resynchronize. This condition follows directly from the retirement semantics: the mechanism expects the next relevant retire event after an indirect transfer to satisfy the landing-pad constraint.
The paper also notes that Zicfilp as specified relies on in-order retirement semantics. Extending it to aggressive out-of-order cores may require retiring the landing pad at retirement time rather than at execution-complete time, and perhaps re-tagging reorder-buffer entries. Hyperthreading and SMT introduce another state-partitioning issue: each hardware thread needs its own cur_label and fp_state, and any shared implementation must prevent inter-thread interference.
Taken together, these caveats delimit the maturity of the current design. The CVA6-CFI implementation demonstrates a compact, single-cycle, low-area landing-pad enforcement mechanism in an in-order core, but it does not claim that the same microarchitectural strategy transfers unchanged to OoO or SMT-heavy designs. A plausible implication is that the architectural abstraction of Zicfilp is stable, while its hardware realization may vary substantially across core classes.