4.4 KiB
Diesel — CWE-407 Disclosure Brief
2026-03-27 · Patch available — awaiting upstream merge
Finding
Three O(n²) defects in Diesel's named-column row access across SQLite and MySQL backends. All patched. Patches ready for upstream review. The defects share a common root cause: named-column access performs a linear scan over column names for every field access on every row.
The Defects
diesel-0001 (PATCHED — MEDIUM): src/sqlite/connection/row.rs
// In SqliteRow (Duplicated variant) — named column access:
fn column_index(&self, name: &str) -> QueryResult<usize> {
self.column_names.iter().position(|n| n == name) // O(C) per call
.ok_or_else(|| ...)
}
column_names is a Vec<&str>. iter().position() is a linear scan over C column names. Called for every named field access on every row in a result set.
diesel-0002 (PATCHED — MEDIUM): src/sqlite/connection/owned_row.rs
// In OwnedSqliteRow — same pattern:
fn column_index(&self, name: &str) -> QueryResult<usize> {
self.column_names.iter().position(|n| n.as_str() == name) // O(C) per call
}
Same root cause on OwnedSqliteRow — the owned variant of the SQLite row type.
diesel-0003 (PATCHED — MEDIUM): src/mysql/connection/row.rs
// In MysqlRow — named column access:
fn column_index(&self, name: &str) -> QueryResult<usize> {
self.metadata.fields().iter().find(|f| f.name() == name) // O(C) per call
.map(|f| f.index())
.ok_or_else(|| ...)
}
metadata.fields() is a slice. iter().find() is O(C) per call. Same pattern on the MySQL backend.
Complexity Proof
For R rows, C columns per row, and A named-column accesses per row:
- Per access: O(C) linear scan over column names
- Per row: A × O(C)
- Total: O(R × A × C)
At R=500 rows, C=100 columns, A=100 accesses per row:
- Defective: 500 × 100 × 100 = 5,000,000 comparisons
- Fixed: 500 × 100 × 1 = 50,000 (BTreeMap index lookup)
- 51× speedup confirmed by unit test
DieselTest.
The fix builds a BTreeMap<String, usize> column index once per prepared statement. All subsequent named-column accesses use O(log C) map lookup instead of O(C) linear scan.
Impact
Diesel is the dominant type-safe Rust ORM — used in Actix-web, Rocket, and other Rust web frameworks. It is known for zero-cost abstractions and compile-time query verification; this defect undermines the runtime performance guarantees.
Named-column access (row.get("column_name")) is a common pattern when using Diesel's #[diesel(column_name = "...")] attribute or dynamic query results. Any application that:
- Fetches wide result sets (many columns)
- Processes large numbers of rows
- Uses named rather than positional column access
...pays this O(R × C²) tax on every query execution. Database-intensive Rust services (API backends, data processing pipelines) are the primary affected workloads.
The Fix
Build a BTreeMap<String, usize> column name index once per statement, reused across all rows:
// Before — O(C) per named access:
self.column_names.iter().position(|n| n == name)
// After
// CWE-407 fix: BTreeMap index built once per statement for O(log C) lookup.
// Built at row construction time:
let column_index: BTreeMap<String, usize> = column_names
.iter()
.enumerate()
.map(|(i, name)| (name.to_string(), i))
.collect();
// Used per access:
column_index.get(name).copied().ok_or_else(|| ...)
For cases where column count is small and bounded, BTreeMap is preferred over HashMap to avoid hash overhead. For large column counts (100+), HashMap is also valid.
Patch
Fix available: defects/diesel/patch/diesel-0001-0003-column-index-btreemap.patch
Three-location patch across sqlite/connection/row.rs, sqlite/connection/owned_row.rs, and mysql/connection/row.rs.
Unit test: DieselTest 2/2 pass. 51× speedup at R=500 rows × C=100 cols × A=100 accesses.
What We Ask
A patch is ready for review.
- Confirm receipt and assign a GitHub issue reference (diesel-rs/diesel).
- Assess severity — all three defects fire on every named-column row access across SQLite and MySQL backends.
- Coordinate a disclosure date — we are targeting 90 days from first contact.
- We will credit the Diesel team in the public disclosure. Preferred acknowledgment format welcome.
Contact: see cover email. This brief is confidential until coordinated disclosure.