java-topology/whitepaper/outreach/solidity.md
russell@unturf.com 0a580b313d undefect. CWE-407 — 63 sites patched across 27 ecosystems
Authors: russell@unturf.com · brackishbert@gmail.com · foxhop.net · TimeHexOn.com

Patches, unit tests, benchmarks, whitepaper, and outreach briefs.
Public domain — no copyright claimed. Use freely.
2026-03-26 17:11:57 -04:00

4.8 KiB
Raw Permalink Blame History

Solidity Compiler — CWE-407 Disclosure Brief

2026-03-26 · Patches available — awaiting upstream merge

Finding

Two O(n²) defects in the Solidity compiler. Both patched. Patches ready for upstream review. One fires on every contract compiled with --via-ir or --optimize — the standard flags for production Ethereum deployment. The developer who wrote it left a // TODO: This algorithm is non-optimal. comment in the same file.

The Defects

solc-0001 (PATCHED — HIGH): libyul/optimiser/CallGraphGenerator.cpp:49

std::find(currentPath.begin(), currentPath.end(), function)

currentPath is a std::vector<YulString> tracking the current DFS path in the Yul call graph cycle detector. For each function visited, the code scans the entire path to detect cycles. Path length grows with call depth D. Over F functions: O(F × D²).

The developer acknowledged this at line 36 of the same file:

// TODO: This algorithm is non-optimal.

solc-0002 (PATCHED — MEDIUM): libevmasm/Assembly.cpp:1077

std::find(items.begin(), items.end(), ...)

Linear scan in EOF relative jump resolution. Fires during bytecode assembly for contracts using the EOF container format. O(J²) where J = jump count.

Complexity Proof (solc-0001)

The Yul call graph cycle detector performs a DFS over the function call graph. At each node:

  • currentPath holds the DFS stack, length up to D (max call depth)
  • std::find on currentPath scans up to D entries
  • Called once per function visit: F total visits
  • Per-visit cost: O(D)
  • Total: O(F × D)

But D itself grows with recursion depth, and in the worst case (a linear call chain), D reaches F. Worst-case total: O(F²).

For a DeFi protocol with 200 internal Yul functions and call depth 50: 200 × 50 = 10,000 vector scans per compilation. Production DeFi contracts (Uniswap v4 hooks, Aave v3, Compound v3) compiled with --via-ir --optimize hit this path on every solc invocation.

Impact

solc-0001 fires on every contract compiled with --via-ir or --optimize. These are the standard flags for:

  • Production Ethereum mainnet deployment
  • Every EVM-compatible chain: Polygon, BNB Chain, Avalanche, Arbitrum, Optimism, Base
  • Smart contract audit toolchains (Slither, Echidna, Certora all shell out to solc)
  • CI pipelines for every DeFi protocol and NFT platform

Compilation time for complex contracts is already a developer pain point. The O(F²) cycle detection is pure overhead — the algorithm is provably suboptimal by the developer's own admission — and it fires on every compile of every non-trivial contract.

solc-0002 affects contracts using EOF (EVM Object Format), the new container format being rolled out in upcoming Ethereum hard forks. This is the right time to fix it — before EOF adoption scales.

The Fix

solc-0001:

// Before
std::vector<YulString> currentPath;
// ... in DFS:
if (std::find(currentPath.begin(), currentPath.end(), function) != currentPath.end())
    // cycle detected

// After
std::vector<YulString> currentPath;          // keep for backtracking order
std::unordered_set<YulString> currentPathSet; // add for O(1) membership
// ... in DFS:
if (currentPathSet.count(function) > 0)
    // cycle detected
// on push: currentPath.push_back(f); currentPathSet.insert(f);
// on pop:  currentPath.pop_back();   currentPathSet.erase(f);

The vector is retained for ordered backtracking. The set is added solely for O(1) cycle detection. This is the standard pattern for DFS cycle detection with path reconstruction.

solc-0002: Replace the linear scan with an std::unordered_map or std::unordered_set keyed on the jump target identifier.

Patches

solc-0001: defects/solc/patch/solc-0001-callgraph-cyclefinder-uset.patch Adds std::set<FunctionHandle> currentPathSet alongside currentPath vector. DFS cycle check switches from std::find (O(D)) to currentPathSet.count() (O(1)). Unit test: 5/5 pass. At F=D=32: defective=15,872 comparisons, fixed=1,024, 15.5× speedup.

solc-0002: defects/solc/patch/solc-0002-assembly-rjump-index.patch Builds std::unordered_map<u256, size_t> tagIndex before the worklist loop. Each RJUMP resolution uses tagIndex.find() (O(1)) instead of std::find over all items (O(N)). Unit test: 4/4 pass. At J=N=100: defective=5,050 comparisons, fixed=200, 25.3× speedup.

What We Ask

  1. Patches are ready for review. Confirm receipt and assign a GitHub issue reference (ethereum/solidity).
  2. Assess severity — solc-0001 in particular has direct supply-chain reach across every EVM chain and audit toolchain.
  3. Coordinate a disclosure date — we are targeting 90 days from first contact.
  4. We will credit the Solidity team in the public disclosure. Preferred acknowledgment format welcome.

Contact: see cover email. This brief is confidential until coordinated disclosure.