transact-rs/sqlx · error · io::Error (InvalidData)

extension names passed to SQLite must not contain nul bytes

Error message

extension names passed to SQLite must not contain nul bytes

What it means

Identical in cause to the entrypoint case, but for the extension's *name*: sqlx converts each extension name into a `CString` for `sqlite3_load_extension`. A name containing an interior NUL byte cannot be represented as a C string, so the connection setup fails early with an `InvalidData` I/O error instead of calling into SQLite.

Source

Thrown at sqlx-sqlite/src/connection/establish.rs:132

        #[cfg(feature = "load-extension")]
        let extensions = options
            .extensions
            .iter()
            .map(|(name, entry)| {
                let entry = entry
                    .as_ref()
                    .map(|e| {
                        CString::new(e.as_bytes()).map_err(|_| {
                            io::Error::new(
                                io::ErrorKind::InvalidData,
                                "extension entrypoint names passed to SQLite must not contain nul bytes"
                            )
                        })
                    })
                    .transpose()?;
                Ok((
                    CString::new(name.as_bytes()).map_err(|_| {
                        io::Error::new(
                            io::ErrorKind::InvalidData,
                            "extension names passed to SQLite must not contain nul bytes",
                        )
                    })?,
                    entry,
                ))
            })
            .collect::<Result<IndexMap<CString, Option<CString>>, io::Error>>()?;

        let thread_id = THREAD_ID.fetch_add(1, Ordering::AcqRel);

        Ok(Self {
            filename,
            open_flags: flags,
            busy_timeout: options.busy_timeout,
            statement_cache_capacity: options.statement_cache_capacity,
            log_settings: options.log_settings.clone(),
            #[cfg(feature = "load-extension")]

View on GitHub (pinned to 03af8bcc57)

Solutions

  1. Sanitize the name: `name.split('\0').next().unwrap_or("")` before calling `.extension(...)`.
  2. Pre-validate with `name.contains('\0')` and reject the configuration at startup with your own error.
  3. Convert C buffers with `CStr` APIs (`from_bytes_until_nul`) rather than `String::from_utf8` on raw bytes.

Example fix

// before
let name = String::from_utf8_lossy(&raw_name).to_string(); // "mod\0pad"
let opts = opts.extension(name);

// after
let name = String::from_utf8_lossy(&raw_name).split('\0').next().unwrap().to_string();
let opts = opts.extension(name);
Defensive patterns

Strategy: validation

Validate before calling

fn validate_extension_name(name: &str) -> Result<&str, String> {
    if name.contains('\0') {
        Err(format!("extension name contains NUL byte: {:?}", name))
    } else if name.is_empty() {
        Err("extension name is empty".into())
    } else {
        Ok(name)
    }
}
// let name = validate_extension_name(&raw_name)?;

Type guard

fn is_cstring_safe(s: &str) -> bool {
    !s.as_bytes().contains(&b'\0')
}

Try / catch

match SqliteConnection::connect_with(&opts).await {
    Ok(conn) => conn,
    Err(e) if e.to_string().contains("must not contain nul bytes") => {
        // strip NUL padding and rebuild options
        ...
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Passing an extension name with an embedded `\0` to `SqliteConnectOptions::extension` (or `extension_argument`'s name pair) and then establishing the connection, e.g. `SqliteConnectOptions::from_url(...)` + `connect`, which runs the mapping loop in `from_options`.

Common situations: Extension names read from raw byte buffers, filenames assembled from fixed-size C arrays that keep NUL padding, or config parsing that does not trim terminators.

Related errors


AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03). Data as JSON: /api/errors/e376bfa7fdd18eff. Report an issue: GitHub.