Stack Overflow in Axum with Cockroachdb
Stack Overflow in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
A Stack Overflow in an Axum service that uses CockroachDB typically occurs when unbounded or recursive data structures are constructed from database rows and then serialized or rendered in a context that causes repeated traversal. In Axum, extractors and handlers build response types that may recursively reference related entities. If a query against CockroachDB returns rows that form a graph (for example, parent-child relationships) and the application constructs nested Rust structs without depth limits, the resulting data structure can grow until the call stack or heap is exhausted.
With CockroachDB, this risk is shaped by how you model and query data. CockroachDB is compatible with PostgreSQL wire protocol and its SQL behavior is largely consistent, but its distributed nature means queries can touch many nodes; a recursive common table expression (CTE) or unbounded join can produce very wide result sets that Axum deserializes into deeply nested structures. If the handler does not bound recursion—by using iterative traversal, DTO projections, or explicit depth limits—a Stack Overflow can manifest either in the application runtime or in serialization logic (e.g., when serializing deeply nested JSON for an API response).
A concrete pattern: an endpoint like GET /org/{id}/tree runs a recursive CTE against CockroachDB to fetch an org hierarchy, then constructs an OrgNode Rust struct that contains a Vec
Additional contributing factors include missing pagination on list endpoints and overly broad SELECT * queries that pull large rows, which increase memory pressure and can exacerbate stack usage when combined with recursive data shapes. Because Axum is type-driven, the compiler cannot inherently prevent runtime deep recursion; developers must enforce limits in the handler and in the SQL layer.
To mitigate this specific combination, use DTOs that flatten the structure, apply explicit depth limits in SQL (e.g., restrict recursive CTEs with MAX), and avoid returning deeply recursive domain models directly from Axum handlers. Combine this with input validation on path/query parameters that could indicate traversal depth, and monitor payload sizes as an early indicator of abuse.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
Remediation focuses on bounding recursion, flattening responses, and using safe SQL patterns. Below are concrete, working examples for Axum with CockroachDB using sqlx.
- Use a DTO with a depth limit instead of recursive domain structs:
// Safe DTO: no recursive nesting
#[derive(serde::Serialize)]
struct OrgNodeDto {
id: i64,
name: String,
parent_id: Option,
depth: i32, // bounded depth
}
- Query with a recursive CTE that enforces a max depth in CockroachDB:
-- CockroachDB SQL: bounded recursive CTE
WITH RECURSIVE org_path AS (
SELECT id, name, parent_id, 0 AS depth
FROM orgs
WHERE id = $1
UNION ALL
SELECT o.id, o.name, o.parent_id, op.depth + 1
FROM orgs o
INNER JOIN org_path op ON o.parent_id = op.id
WHERE op.depth < 10 -- enforce max depth
)
SELECT id, name, parent_id, depth FROM org_path;
- Axum handler that uses the bounded query and returns the DTO:
// Axum handler with bounded depth and flattened output
async fn get_org_tree(
Path(id): Path,
pool: Extension,
) -> Result {
let rows = sqlx::query_as!(OrgNodeDto,
r#"
WITH RECURSIVE org_path AS (
SELECT id, name, parent_id, 0 AS depth
FROM orgs
WHERE id = $1
UNION ALL
SELECT o.id, o.name, o.parent_id, op.depth + 1
FROM orgs o
INNER JOIN org_path op ON o.parent_id = op.id
WHERE op.depth < 10
)
SELECT id, name, parent_id, depth FROM org_path
"#,
id
)
.fetch_all(pool.get_ref())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(rows))
}
- For list endpoints, enforce server-side pagination to limit result size:
// Paginated, non-recursive list endpoint
async fn list_orgs(
pool: Extension,
Query(params): Query<ListParams>,
) -> Result {
let org_list = sqlx::query_as!(OrgSummary,
"SELECT id, name FROM orgs LIMIT $1 OFFSET $2",
params.limit,
params.offset
)
.fetch_all(pool.get_ref())
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(Json(org_list))
}
- Validate path/query parameters to prevent deep traversal requests:
// Validate depth-like inputs early
fn validate_depth(depth: Option<i32>) -> Result<i32, (StatusCode, String)> {
match depth {
Some(d) if d >= 0 && d <= 100 => Ok(d),
_ => Err((StatusCode::BAD_REQUEST, "invalid depth".to_string())),
}
}
These patterns ensure the Axum + CockroachDB stack avoids unbounded recursion and keeps response sizes predictable, reducing the risk of Stack Overflow and related denial-of-service conditions.