Null Pointer Dereference in Axum with Cockroachdb
Null Pointer Dereference in Axum with Cockroachdb — how this specific combination creates or exposes the vulnerability
A null pointer dereference in an Axum service that uses CockroachDB typically arises when application code assumes the result of a database query or an option type is non-nil without validating it. Because CockroachDB is a distributed SQL database, Axum routes often perform SQL operations that return rows as structs or as optional fields, and Go’s handling of pointers and nil values interacts directly with how results are scanned.
In Axum, handlers commonly accept strongly typed request extractors and call service functions that query CockroachDB. If a query uses QueryRow and the row does not exist, Scan can leave destination variables uninitialized, and failing to check the sql.ErrNoRows error can lead to passing a nil pointer into downstream logic. This becomes a vulnerability when unchecked nils are dereferenced, causing a runtime panic that may be exposed to the client through stack traces or generic error pages.
The risk is compounded when Axum routes rely on middleware that expects certain request-scoped values (e.g., authenticated user claims) derived from a database row. If the row is missing and the code does not guard against nil, the middleware may attempt to access fields on a nil receiver, triggering a null pointer dereference before reaching the intended business logic. This is especially relevant when using CockroachDB’s secondary indexes or joins that can return empty result sets under certain conditions, such as soft-deleted rows or tenant-isolation constraints.
Moreover, when using struct scanning with libraries that map rows to Go structs, unexported fields or omitted pointers can remain nil if not explicitly initialized. Axum handlers that directly serialize these structs into JSON responses may inadvertently expose internal nil pointers if reflection-based serialization does not handle them gracefully, potentially leading to information leakage or unstable responses under error conditions.
Cockroachdb-Specific Remediation in Axum — concrete code fixes
To prevent null pointer dereference in Axum with CockroachDB, always validate query results and handle optional fields explicitly. Use proper error handling for sql.ErrNoRows and ensure pointers are either initialized or checked before dereferencing. Below are concrete, working examples tailored to Axum handlers and CockroachDB interactions.
Example 1: Safe QueryRow with Error Handling
// handler.rs
use axum::extract::Path;
use sqlx::postgres::PgRow;
use sqlx::Row;
use std::sync::Arc;
pub async fn get_user_handler(
db: Arc<sqlx::PgPool>,
Path(user_id): Path<i32>,
) -> Result<impl axum::response::IntoResponse, (axum::http::StatusCode, String)> {
let row = sqlx::query(
"SELECT id, email, tenant_id FROM users WHERE id = $1 AND deleted_at IS NULL"
)
.bind(user_id)
.fetch_optional(&*db)
.await
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let user = match row {
Some(r) => r,
None => return Err((axum::http::StatusCode::NOT_FOUND, "User not found".into())),
};
let email: String = user.try_get("email")
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
let tenant_id: i32 = user.try_get("tenant_id")
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(axum::Json(serde_json::json!({
"email": email,
"tenant_id": tenant_id
})))
}
Example 2: Struct Scanning with Option for Nullable Fields
// models.rs
use sqlx::FromRow;
use serde::Serialize;
#[derive(FromRow, Serialize)]
pub struct User {
pub id: i32,
pub email: String,
#[sqlx(rename = "tenant_id")]
pub tenant_id: i32,
#[sqlx(rename = "profile_data")]
pub profile_data: Option<serde_json::Value>, // nullable field handled explicitly
}
// handler.rs
use axum::extract::State;
use sqlx::PgPool;
pub async fn list_users_handler(
State(pool): State<Arc<PgPool>>,
) -> Result<impl axum::response::IntoResponse, (axum::http::StatusCode, String)> {
let users = sqlx::query_as::<_, User>(
"SELECT id, email, tenant_id, profile_data FROM users WHERE tenant_id = $1"
)
.bind(1_i32)
.fetch_all(&*pool)
.await
.map_err(|e| (axum::http::StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
Ok(axum::Json(users))
}
Example 3: Middleware Guard with Database Lookup
// middleware.rs
use axum::extract::Request;
use axum::http::StatusCode;
use sqlx::PgPool;
use std::future::Future;
use std::pin::Pin;
pub async fn validate_tenant_middleware(
pool: &PgPool,
request: Request,
) -> Result<Request, (StatusCode, String)> {
// Assume tenant_id extracted from header or JWT
let tenant_id: i32 = extract_tenant_id(&request).map_err(|_| (StatusCode::UNAUTHORIZED, "Invalid tenant".into()))?;
let exists: (bool,) = sqlx::query_as(
"SELECT EXISTS(SELECT 1 FROM tenants WHERE id = $1 AND active = TRUE)"
)
.bind(tenant_id)
.fetch_one(pool)
.await
.map_err(|e| (StatusCode::INTERNAL_SERVER_ERROR, e.to_string()))?;
if exists.0 {
Ok(request)
} else {
Err((StatusCode::FORBIDDEN, "Tenant access denied".into()))
}
}
These patterns ensure that optional query results are explicitly handled, pointers are checked before use, and errors like sql.ErrNoRows are converted into appropriate HTTP responses rather than allowing nil dereferences to propagate.
Frequently Asked Questions
How can I test for null pointer risks in my Axum + CockroachDB API using middleBrick?
middlebrick scan https://your-api.example.com. The report will highlight unchecked database query results and nil handling patterns that can lead to null pointer dereferences, along with remediation guidance.