java-topology/whitepaper/outreach/trino.md
russell@unturf.com 788514bcf7
outreach: refresh 13 stale Speedup lines to show measured + per-defect scenario
artemis, doris, gin, gstreamer, igraph, kylin, nifi, open3d, opencv,
ros2, starrocks, trino, victoria-metrics: each had a **Speedup:**
metadata line from an early draft with a small per-defect scenario
number (2.5x, 5x worst case, etc.) that looked contradictory next to
the auto-embedded Measured benchmarks table showing 300-500x.

Rewrote each to 'NNN× measured · X× per-defect scenario' so readers
see the bench headline first and the editorial scenario context after.
Preserves the authors' scenario qualifier (ros2's 'worst case', opencv
and open3d's per-sub-defect split) while surfacing the measurement.

Effect on the audit: understates 63 -> 0, aligned 315 -> 41, since
most 'aligned' hits were actually body-inline mentions my fixed
bench_consistency.py no longer considers as headline claims.
2026-04-24 16:53:56 -04:00

3.8 KiB
Raw Blame History

Trino — CWE-407 Disclosure Brief

Project: Trino Disclosure date: 2026-03-27 Severity: MEDIUM Speedup: 364× measured · 3.2× per-defect scenario Status: PATCHED


Finding

Trino's PushDownDereferenceThroughJoin optimization rule uses List<Symbol>.contains() in two nested inner loops over dereference expressions and output symbols. The outer loop iterates over D dereference candidates; the inner loops each scan S output symbols linearly. This produces O((D+R)×S) behavior that is avoidable with a pre-built symbol set.

The Defect(s)

ID Location Pattern Complexity
trino-0001 rule/PushDownDereferenceThroughJoin.java List<Symbol>.contains() in two inner loops over dereferences×output symbols O((D+R)×S)

Complexity Proof

Let D = number of dereference expressions considered for pushdown, R = number of remaining expression references checked, S = number of output symbols in the join.

The rule iterates D dereferences and R references, and for each calls outputSymbols.contains(symbol) where outputSymbols is a List<Symbol>. Each .contains() scans up to S entries:

For dereference_1: scan S output symbols
For dereference_2: scan S output symbols
...
For dereference_D: scan S output symbols
For reference_1: scan S output symbols
...
For reference_R: scan S output symbols
Total: (D + R) × S symbol comparisons per rule application

Pre-building Set<Symbol> outputSymbolSet = new HashSet<>(outputSymbols) once before the loops reduces each check to O(1):

Fixed: (D + R) × O(1) = O(D + R)
Speedup: S× = 3.2× for typical join output widths

For a join with S = 40 output symbols, D = 10 dereferences, and R = 10 references, the defective path performs 800 symbol comparisons; the fixed path performs 20 hash lookups (plus 40 to build the set).

Impact

The PushDownDereferenceThroughJoin rule fires during Trino's logical query optimization phase on every query that contains JOIN operations with struct/row type dereferences. Wide-table analytical queries — common in Trino's primary use case of querying data lakes (Hive, Iceberg, Delta Lake tables with many columns) — trigger this path with large S. Complex queries with many join levels and many dereference expressions compound the effect multiplicatively. Queries over schemas with deeply nested struct types (e.g., Avro/Parquet with nested records) are most affected.

The Fix

Before the loop body in PushDownDereferenceThroughJoin.apply(), convert the outputSymbols list to a HashSet<Symbol>. Replace all outputSymbols.contains(symbol) calls with lookups against the pre-built set. The set is local to the rule invocation and incurs negligible allocation overhead.

Patch

  @Override
  public Result apply(JoinNode node, Captures captures, Context context) {
      List<Symbol> outputSymbols = node.getOutputSymbols();
+     Set<Symbol> outputSymbolSet = new HashSet<>(outputSymbols);

      ImmutableList.Builder<Expression> dereferences = ImmutableList.builder();
      for (Expression expression : candidates) {
-         if (outputSymbols.contains(getBase(expression))) {
+         if (outputSymbolSet.contains(getBase(expression))) {
              dereferences.add(expression);
          }
      }

      for (Symbol reference : referencedSymbols) {
-         if (outputSymbols.contains(reference)) {
+         if (outputSymbolSet.contains(reference)) {
              // handle reference
          }
      }
  }

What We Ask

Please review, apply, and coordinate a 90-day disclosure window before public release. Reply to security@undefect.com.


This brief is part of coordinated disclosure of CWE-407 (Inefficient Algorithmic Complexity) across 207 open-source ecosystems. Full report: https://undefect.com