transact-rs/sqlx · error · io::Error (InvalidData)
extension entrypoint names passed to SQLite must not contain
Error message
extension entrypoint names passed to SQLite must not contain nul bytes
What it means
When loading a SQLite extension, sqlx converts the entrypoint name into a `CString` (a NUL-terminated C string) to pass to the SQLite C API. Rust strings may legally contain interior NUL bytes (`\0`), but C strings cannot, so `CString::new` fails. sqlx maps that failure to an `io::Error` of kind `InvalidData` rather than panicking.
Source
Thrown at sqlx-sqlite/src/connection/establish.rs:123
}
let filename = CString::new(filename).map_err(|_| {
io::Error::new(
io::ErrorKind::InvalidData,
"filename passed to SQLite must not contain nul bytes",
)
})?;
#[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>>()?;
View on GitHub (pinned to 03af8bcc57)
Solutions
- Strip everything from the first NUL byte before passing the entrypoint: `entry.split('\0').next().unwrap_or("")`.
- Validate the string with `entry.contains('\0')` and return a clear application-level error before building `SqliteConnectOptions`.
- If the bytes come from a C API, use `CStr::from_bytes_until_nul` (or `CStr::from_ptr`) and convert with `to_string_lossy` instead of treating the raw buffer as a Rust `String`.
Example fix
// before
let entry = std::str::from_utf8(&buf).unwrap().to_string(); // may contain \0
opts = opts.extension(ext_name).with_argument(entry);
// after
let entry = std::str::from_utf8(&buf)?.split('\0').next().unwrap().to_string();
opts = opts.extension(ext_name).with_argument(entry); Defensive patterns
Strategy: validation
Validate before calling
fn validate_entrypoint(entry: &str) -> Result<&str, String> {
if entry.contains('\0') {
Err(format!("extension entrypoint contains NUL byte: {:?}", entry))
} else {
Ok(entry)
}
}
// let entry = validate_entrypoint(&raw_entry)?; 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") => {
// sanitize inputs and retry
...
}
Err(e) => return Err(e),
} Prevention
- Always derive extension names/entrypoints from `CStr`/`OsStr` APIs, never from raw byte buffers.
- Sanitize with `split('\0').next()` at the config-parsing boundary.
- Add a unit test asserting options construction fails fast on `\0` inputs.
When it happens
Trigger: Calling `SqliteConnectOptions::extension` / `SqliteConnectOptions::extension_argument` with an entrypoint string containing an interior `\0` byte (e.g. built from truncated buffers or C-side data), then connecting via `SqliteConnection::connect` / `AnyPool` which routes through `from_options` in establish.rs.
Common situations: Loading extension entrypoints read from binary sources, `String::from_utf8` of buffers that include NUL terminators, or dynamically constructed entrypoint names where a terminator was not stripped.
Related errors
- extension names passed to SQLite must not contain nul bytes
- 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/3ab4f991f817b562.
Report an issue: GitHub.