Skip to content

SQLite: query!() segfaults rustc when a column has no declared type #4373

Description

@msdrigg

I have found these related issues/pull requests

Description

StatementHandle::column_nullable asks sqlite3_table_column_metadata() for a column's declared type and then calls CStr::from_ptr() on the result without checking for null:

let datatype = CStr::from_ptr(datatype);

SQLite sets that out-param to NULL when the column was declared with no type at all, which is legal:

CREATE TABLE users (device_id PRIMARY KEY, name TEXT);

So describing that table dereferences a null pointer and the process dies with SIGSEGV. Because the query!() family describes a live database during macro expansion, the process that dies is rustc itself:

error: rustc interrupted by SIGSEGV, printing backtrace
  2  core::ffi::c_str::CStr::from_ptr
  3  sqlx_sqlite::statement::handle::StatementHandle::column_nullable
  4  sqlx_sqlite::connection::describe::describe
  5  sqlx_sqlite::describe_blocking
  6  sqlx_macros_core::database::impls::...::describe_blocking
  7  sqlx_macros_core::query::expand_with

The practical effect is that a crate can't be compiled online or run cargo sqlx prepare at all if any table in the database has an untyped column, even when no query touches that table. An existing .sqlx cache keeps working, so offline builds are unaffected, but the cache can't be regenerated.

This is a regression in 0.9.0. Before #4088 the declared-type slot was passed as ptr::null_mut() and never read.

The sibling function column_decltype, twenty lines up, already null-checks the equivalent pointer from sqlite3_column_decltype().

Reproduction steps

cargo new sqlx-repro && cd sqlx-repro
cargo add tokio --features macros,rt-multi-thread
cargo add sqlx@0.9.0 --no-default-features --features runtime-tokio,sqlite,macros

sqlite3 repro.db "CREATE TABLE users (device_id PRIMARY KEY, name TEXT);"

src/main.rs:

#[tokio::main]
async fn main() {
    let pool = sqlx::SqlitePool::connect(&std::env::var("DATABASE_URL").unwrap())
        .await
        .unwrap();
    let row = sqlx::query!("SELECT device_id, name FROM users")
        .fetch_optional(&pool)
        .await
        .unwrap();
    println!("{:?}", row.map(|r| r.name));
}
DATABASE_URL="sqlite://repro.db" cargo build

Crashes with signal: 11, SIGSEGV. Changing device_id PRIMARY KEY to device_id TEXT PRIMARY KEY and rebuilding makes it compile, which isolates the untyped column as the trigger.

SQLx version

0.9.0

Enabled SQLx features

runtime-tokio, sqlite, macros (with default-features = false) is enough to reproduce

Database server and version

SQLite 3.51.3

Operating system

macOS

Rust version

rustc 1.96.1 (31fca3adb 2026-06-26)

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions