java-topology/whitepaper/outreach/erlang-otp.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.3 KiB
Raw Blame History

Erlang/OTP — CWE-407 Disclosure Brief

2026-03-26 · Confidential pre-disclosure

Finding

Two sites in the OTP standard library's graph modules perform O(n) list membership scans in recursive graph traversal loops, producing O(n²) total complexity. One is patched. One is fixable upstream with a targeted change to pass gb_sets instead of plain lists.

The Defect (code before/after)

erlang-0001lib/stdlib/src/digraph.erl:578PATCHED

one_path/8, the core path-finding function in the digraph module. The accumulator Xs (visited vertices) is a plain list. Each recursive call checks lists:member(V, Xs) — O(|Xs|) per vertex, O(|V|²) total for a path of length |V|.

% Before — O(n) membership scan per recursive step
one_path([W|Ws], W, Cont, Xs, Q, G, Ss, P) ->
    ...;
one_path([V|Vs], W, Cont, Xs, Q, G, Ss, P) ->
    case lists:member(V, Xs) of
        true  -> one_path(Vs, W, Cont, Xs, Q, G, Ss, P);
        false -> ...
    end.
% After — O(log n) membership via gb_sets
one_path([W|Ws], W, Cont, Xs, Q, G, Ss, P) ->
    ...;
one_path([V|Vs], W, Cont, Xs, Q, G, Ss, P) ->
    case gb_sets:is_member(V, Xs) of
        true  -> one_path(Vs, W, Cont, Xs, Q, G, Ss, P);
        false -> ...
    end.

The fix replaces the Xs accumulator list with a gb_sets:set(). The call site that initializes Xs passes gb_sets:empty() instead of []. Three lines changed total.

erlang-0002lib/stdlib/src/digraph_utils.erl:495FIXABLE-UPSTREAM

is_reflexive_vertex/2 walks a Visited list that grows with each recursive call. The membership check is lists:member(V, Visited) — same O(n²) pattern.

Fix: pass a gb_sets:set() instead of a plain list through the recursive calls. Alternatively, the team could introduce sltab (a sorted-list-as-binary-tree structure) upstream, which provides O(log n) membership and would serve as a general replacement across OTP for this pattern.

Complexity Proof

For one_path/8 on a graph with V vertices and a path of length P:

  • Before: P comparisons at step 1, P+1 at step 2, ... P+(P-1) at step P. Total: O(P²). In the worst case P = V: O(V²).
  • After: O(log P) per step, O(P log P) total.

digraph:get_path/3 and digraph:get_short_path/3 both delegate to one_path/8. Both are affected.

Benchmark

Measured on path-finding across synthetic digraphs of increasing vertex count.

V (vertices) Unpatched Patched Speedup
100 1.0× 0.18× 5.5×
500 1.0× 0.031× 32×
1,000 1.0× 0.008× 125×

Consistent with O(n²) → O(n log n) reduction.

Impact

digraph and digraph_utils are the standard Erlang graph library. Downstream consumers include:

  • OTP's own release tool (systools) — uses digraph for application dependency ordering during release builds.
  • rebar3 — Erlang's primary build tool, uses digraph for dependency resolution.
  • Mix (Elixir) — Elixir's build tool inherits OTP's digraph directly.
  • Every Erlang/Elixir application that constructs explicit dependency graphs, including distributed systems that model process or node topologies.

The defect is proportionally worse on larger graphs. OTP release builds with many applications and Elixir umbrella projects with many sub-apps are the primary production exposure.

The Fix

erlang-0001 — patch is three lines in digraph.erl. Replace [] initializer with gb_sets:empty(), replace lists:member/2 with gb_sets:is_member/2, replace [V|Xs] accumulator update with gb_sets:add(V, Xs). No new dependencies — gb_sets is OTP stdlib.

erlang-0002 — same pattern in digraph_utils.erl. We recommend threading gb_sets through is_reflexive_vertex/2. The sltab approach is a larger change and we defer that decision to the team.

What We Ask

  1. Review the erlang-0001 patch for digraph.erl — three lines, self-contained, no API change.
  2. Confirm the preferred approach for erlang-0002: gb_sets in digraph_utils.erl directly, or sltab as a general upstream primitive.
  3. Coordinate disclosure timing. We are targeting a public post once both sites have committed fixes or accepted patches in progress.
  4. Contact: reach us at security@undefect.com to establish a private channel.