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
- Sanitize the name: `name.split('\0').next().unwrap_or("")` before calling `.extension(...)`.
- Pre-validate with `name.contains('\0')` and reject the configuration at startup with your own error.
- 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
- Normalize names coming from C sources with `CStr::from_bytes_until_nul(...).to_string_lossy()`.
- Validate all extension options once at startup, before building connection pools.
- Keep extension config in UTF-8 text files/vars, not fixed-size binary buffers.
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
- extension entrypoint names passed to SQLite must not contain
- SQLite is unable to allocate memory to hold the sqlite3 obje
- invalid column index: {}
- unimplemented!()
- expected to read {} bytes, got {} bytes at EOF
AI-assisted analysis of transact-rs/sqlx@03af8bcc57 (2026-09-03).
Data as JSON: /api/errors/e376bfa7fdd18eff.
Report an issue: GitHub.