cf-ambiguous instruction
In the Cascade CPU fuzzing framework, a cf-ambiguous instruction is a control-flow instruction whose required operand value is not known when the instruction is first selected during program generation. The canonical examples are indirect jumps such as jalr, which must be supplied with a specific target value val that Cascade intends to impose on the CPU under test. Because val is not yet determined, Cascade cannot simply pre-compute a static target operand; instead, it must take special steps to materialize val later, which has implications for instruction set simulator (ISS) pre-simulation and overall fuzzing throughput.
Why such instructions are problematic
If Cascade relies on a per-instruction ISS execution to keep the simulator's state consistent with the program being generated, then for every cf-ambiguous instruction the ISS cannot advance to the next basic block — and cannot correctly service the pending memory operation — without first being executed at least once with the real operand value. Treating each cf-ambiguous instruction this way becomes a critical performance bottleneck.
Construction under the dependency-preservation principle
Cascade applies the principle of dependency preservation: rather than constraining the randomized data flow, it makes the imposed target value val depend on a randomly chosen register rd whose value is already produced by the program’s data flow and is therefore unknown when the cf-ambiguous instruction is picked. To obtain val from the unknown rd, Cascade introduces:
- An offset register
r_off, whose value is fixed at generation time so that, combined withrd, it producesval. - An offset applier instruction (e.g.,
xor) that combinesrdandr_offand writes the intended valuevalinto an applied registerr_app. - Optionally, a third register may be used as
r_appto maximize the degrees of freedom;rdandr_offare not required to be reused as the destination.
Branches are handled differently: because applied registers are a precious resource, Cascade uses ISS feedback to obtain the operand values and selects a suitable branch opcode (still or hopping) rather than the offset-applier scheme used for cf-ambiguous instructions.
Offset state machines
To make the construction well-formed, Cascade maintains a simple state machine per architectural integer register enforcing three conditions:
r_appmust be available when the cf-ambiguous instruction is picked.r_offmust be ready before the offset applier instruction executes.rdmust be available when the offset applier instruction executes.
Registers traverse free, gen, ready, and unrel states during the offset construction cycle, with transitions forming a specific instruction category in Cascade.
Mitigation: asymmetric ISA pre-simulation
To avoid the per-cf-ambiguous-instruction ISS execution cost, Cascade introduces asymmetric ISA pre-simulation. Instead of feeding the ISS the ultimate program the CPU under test will execute, Cascade provides an intermediate program satisfying three invariants:
- (a) the values of all dependent registers (in the constructions of applied registers) are identical between the intermediate and ultimate programs,
- (b) the programs' control flows are identical, and
- (c) in the intermediate program, no applied register depends on the randomized data flow.
Concretely, the intermediate program differs from the ultimate program in three ways:
- The immediates
imm1andimm2are chosen so thatr_offis set directly toval(bypassing the unknownrd). - The offset-applying instruction is replaced with an
mvthat copiesr_offintor_app. - Non-taken branches are transformed into NOPs, because their operand values are not yet known.
With these transformations, the intermediate program complies with the three invariants and the ISS only needs to be executed once for the whole program rather than once per cf-ambiguous instruction.
Intermediate register states
The offset construction scheme guarantees by design that at any point in the program, all free and applied registers have the same value in both the intermediate and the ultimate program. This invariant does not hold for registers in the gen, ready, and unrel states; therefore, those registers must never be used as the input of any instruction other than the next instruction in the offset construction cycle (steps (b) and (c) in Figure 4 of the source paper).