Skip to main content
Sigvex

Arbitrary Jump Remediation

How to eliminate control-flow hijacking by never deriving a JUMP destination from user input — use structured dispatch instead of computed jumps.

Arbitrary Jump Remediation

Overview

An arbitrary jump happens when a JUMP/JUMPI destination is derived from calldata, letting an attacker steer execution to any JUMPDEST in the contract — past a require, into a guarded branch, or straight to fund-moving code. The EVM restricts jumps to valid JUMPDEST markers, but that is not access control: any reachable JUMPDEST is a candidate landing point. The fix is to never compute a jump target from untrusted input. Use high-level control flow (Solidity if/switch over a typed action), which the compiler lowers to jumps it controls.

Related Detector: Arbitrary Jump

Before (Vulnerable)

function dispatch(uint256 dest) external {
    assembly {
        jump(dest)   // attacker picks the execution target
    }
}

After (Fixed)

function dispatch(uint256 action) external {
    if (action == 0) {
        _deposit();
    } else if (action == 1) {
        _withdraw();
    } else {
        revert("Unknown action");
    }
}

function _deposit() internal { /* ... */ }
function _withdraw() internal { /* ... */ }

The set of reachable targets is now fixed at compile time, and an unrecognized value reverts instead of jumping somewhere unintended.

Alternative Mitigations

Bounds-check before any computed dispatch — if a jump table is genuinely required in hand-written Yul/assembly, validate the index against a known small range and map it through a constant table of compile-time labels, never the raw user value.

Avoid hand-rolled assembly dispatch entirely — in the vast majority of contracts, the Solidity function selector mechanism already provides safe dispatch. Reserve raw jump for cases where you can prove the destination is compiler-controlled.

Common Mistakes

Trusting the JUMPDEST restriction as a safeguard — landing only on JUMPDESTs still lets an attacker reach internal entry points that were never meant to be called directly. Validity of the destination is not authorization.

Deriving the index from calldata and masking it — masking a user value to a range still lets the attacker choose among all in-range targets. The destinations themselves must be constants you selected.

References