3.5 KiB
Apache Doris — CWE-407 Disclosure Brief
Project: Apache Doris Disclosure date: 2026-03-27 Severity: MEDIUM Speedup: 2.5× Status: PATCHED
Finding
Apache Doris's BindExpression rule in the Nereids query optimizer calls groupingExprs.contains() on a List inside a loop over aggregate expressions in non-FULL_GROUP_BY mode. For P aggregate expressions each checked against G grouping expressions, this is O(P×G) per query binding pass. The defect is in the expression analysis hot path that runs for every aggregation query.
The Defect(s)
| ID | Location | Pattern | Complexity |
|---|---|---|---|
| doris-0001 | nereids/rules/analysis/BindExpression.java |
groupingExprs.contains() O(P×G) per aggregate in non-FULL_GROUP_BY mode |
O(P×G) |
Complexity Proof
Let P = number of aggregate output expressions, G = number of grouping expressions in the GROUP BY clause.
During expression binding in non-FULL_GROUP_BY mode, BindExpression iterates over all P aggregate output expressions. For each, it calls groupingExprs.contains(expr) where groupingExprs is a List — an O(G) linear scan:
For agg_expr_1: groupingExprs.contains() scans G entries
For agg_expr_2: groupingExprs.contains() scans G entries
...
For agg_expr_P: groupingExprs.contains() scans G entries
Total: P × G comparisons per query binding
Pre-building Set<Expression> groupingSet = new HashSet<>(groupingExprs) before the aggregate loop reduces each check to O(1):
Fixed: build O(G) + P × O(1) = O(G + P)
Speedup: G× at the inner check; net ~2.5× for typical P/G ratios
For P = 25 aggregate expressions and G = 10 grouping expressions, the defective path performs 250 expression comparisons; the fixed path performs 10 (set build) + 25 (lookups) = 35.
Impact
All Apache Doris queries using non-FULL_GROUP_BY aggregation with a meaningful number of SELECT expressions are affected. This covers a large fraction of analytical SQL queries — reporting queries with many measured dimensions, BI tool-generated SQL with complex GROUP BY clauses, and ad-hoc analytical queries on Doris tables. The defect is in the Nereids optimizer's expression binding phase, which runs before cost-based optimization; query planning latency increases with P×G for every affected query. Interactive analytics workloads and high-QPS reporting dashboards are most sensitive to optimizer latency.
The Fix
Before the aggregate expression iteration loop in the relevant BindExpression.bind() method path, build a HashSet<Expression> from groupingExprs. Replace groupingExprs.contains(expr) with groupingSet.contains(expr) throughout the loop body.
Patch
private Plan bindAggregate(MatchingContext<LogicalAggregate<Plan>> ctx) {
List<Expression> groupingExprs = aggregate.getGroupByExpressions();
+ Set<Expression> groupingSet = new HashSet<>(groupingExprs);
List<NamedExpression> outputExprs = aggregate.getOutputExpressions();
for (NamedExpression outputExpr : outputExprs) {
- if (!groupingExprs.contains(outputExpr)) {
+ if (!groupingSet.contains(outputExpr)) {
// validate non-grouping aggregate 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