Skip to main content
Sigvex

Solidity Calldata Re-encoding Miscompilations

Flags contracts compiled with Solidity versions affected by the 2022 ABI-encoder calldata re-encoding bugs that can corrupt forwarded calldata.

Solidity Calldata Re-encoding Miscompilations

Overview

Two closely related compiler bugs in the Solidity ABI encoder corrupt data when calldata is re-encoded directly into an external call. The first (advisory 2022-05) skipped the calldatasize() bounds check on the direct calldata-to-encoder path for nested dynamic arrays, allowing malformed calldata to be re-emitted into a contract call without detection. The second (advisory 2022-08) performed an over-eager 32-byte cleanup after a statically sized trailing array, zeroing the first 32 bytes of the next dynamic tuple component during re-encoding.

Both bugs surface only when a contract forwards complex calldata-sourced arguments — nested dynamic arrays, or tuples with dynamic trailing components — into another call. The 2022-05 bug is fixed in 0.8.14; the 2022-08 bug remains until 0.8.16. A contract compiled at 0.8.14 is therefore still exposed to the second bug.

Sigvex identifies affected contracts from recovered compiler metadata combined with a runtime signal. It reports a finding when the compiler version falls inside an advisory’s affected range and the bytecode contains at least one calldata-copy operation, which is the entry point for the re-encoding path these bugs target. Because two advisories overlap, a single contract in the shared range produces a finding for each.

Why This Is an Issue

When the encoder skips the size check or overwrites a tuple head, the contract forwards data that no longer matches what the caller supplied. A downstream call can execute against silently altered arguments — a corrupted recipient, amount, or array element — without any revert. Contracts that act as routers, multicall aggregators, or middleware that pass user-supplied structured calldata onward are the most exposed, because forwarding complex calldata is exactly their job.

How to Resolve

Recompile with a fixed compiler and redeploy. Use 0.8.16 or later to clear both advisories at once; 0.8.14 alone clears only the 2022-05 bug.

// Affected: compiled with an unfixed compiler (e.g. 0.8.13)
pragma solidity 0.8.13;

contract Router {
    // Forwards calldata-sourced nested dynamic arguments into a call.
    function forward(address target, bytes[] calldata payloads) external {
        target.call(abi.encodeWithSelector(0x12345678, payloads));
    }
}

// Fixed: pin to a compiler past both advisories
pragma solidity 0.8.16;

If an immediate upgrade is not possible, manually audit every external call that forwards calldata-sourced arguments of type T[] calldata where T is a dynamic tuple or a nested dynamic array, and confirm the forwarded data is not corrupted.

Detection Methodology

The detector pairs compiler-version metadata with a bytecode precondition. It checks each advisory’s half-open affected range against the recovered version, then requires a calldata-copy instruction to be present anywhere in the recovered instruction stream. That opcode is the gateway for the calldata re-encoding path: encoders that work purely from memory, or that encode literal or storage values, never use it, so a contract without it cannot exhibit the bug. Each matching advisory contributes its own finding. The detector does not prove that the calldata copy is followed by an encoder invocation, so its confidence is held at a moderate level rather than treated as certain.

Sample Sigvex Output

{
  "detector_id": "solc-calldata-reencode-bugs",
  "severity": "medium",
  "confidence": 0.75,
  "description": "solc-2022-08-calldata-tuple-head-overflow: Contract was compiled with Solidity 0.8.14, which is in the affected range [0.5.8, 0.8.16). Runtime corroboration: contract contains at least one CALLDATACOPY.",
  "location": {
    "function": "forward",
    "offset": 412
  }
}

Limitations

False positives are possible: the presence of a calldata-copy opcode does not prove the contract reaches the affected encoder path with a vulnerable argument shape, and a contract in range may never forward the specific T[] calldata types that trigger the bug. Confidence drops when compiler metadata is unreliable. False negatives occur when compiler metadata cannot be recovered, since the detector cannot place the contract in a version range without it.

  • Outdated Compiler — general detection of compiler versions with known bugs or missing safety features.
  • Storage Write Removal — another optimizer/codegen miscompilation tied to a specific compiler-version window.

References