Rust local Row::get_value truncates embedded NUL TEXT and panics on malformed UTF-8 (0.9.30)
Evaluación
- Dificultad
- 2/5
- Tiempo estimado
- 1-3 horas
- Aptitud para principiantes
- 82/100
Línea de trabajo
Empieza en libsql/src/local/rows.rs, comparando Row::get_value con la ruta get_ref existente que conserva la longitud exacta en bytes. Verifica que los valores TEXT propios conserven los bytes NUL incrustados y devuelvan un error para UTF-8 no válido, mientras que los valores NULL, numéricos, BLOB, vacíos y Unicode mantengan sus tipos esperados; ejecuta las pruebas locales relevantes de Rust o añade regresiones si falta cobertura.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
The local Rust driver in libsql 0.9.30 silently truncates owned TEXT values at an embedded NUL, and panics rather than returning an error when the stored TEXT contains malformed UTF-8. The SQLite value remains intact; the loss happens in the owned-value conversion.
Reproduced on macOS arm64 with Rust 1.98.1, using registry libsql/libsql-sys/libsql-ffi 0.9.30 and the local core feature. There is no downstream patch in this reproduction.
Cargo.toml:
[package]
name = "libsql-owned-text-report"
version = "0.1.0"
edition = "2024"
[dependencies]
libsql = { version = "=0.9.30", default-features = false, features = ["core"] }
tokio = { version = "=1.47.1", features = ["rt", "macros"] }
src/main.rs:
use libsql::{Builder, Value};
#[tokio::main(flavor = "current_thread")]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
let database = Builder::new_local(":memory:").build().await?;
let connection = database.connect()?;
let mut rows = connection.query("SELECT ?1", ["a\0b"]).await?;
let row = rows.next().await?.unwrap();
println!("expected {:?}, actual {:?}",
Value::Text("a\0b".into()), row.get_value(0)?);
drop(row);
drop(rows);
let mut rows = connection.query("SELECT CAST(x'80' AS TEXT)", ()).await?;
let row = rows.next().await?.unwrap();
let result = std::panic::catch_unwind(
std::panic::AssertUnwindSafe(|| row.get_value(0))
);
println!("malformed UTF-8 panicked={}", result.is_err());
Ok(())
}
Observed:
expected Text("a\0b"), actual Text("a")
... value.rs:237:40: called Result::unwrap() on an Err value: Utf8Error ...
malformed UTF-8 panicked=true
Expected: the first owned value preserves all three bytes; the second returns a decoding error through Result instead of panicking. Truncation also makes distinct persisted TEXT keys appear identical to code reading owned values.
The local Row::get_value path converts sqlite3_column_text using a NUL-terminated CStr, then the owned FromValue conversion unwraps UTF-8 decoding. The existing Row::get_ref path already obtains the exact byte length. A downstream correction detaches those ValueRef values, preserving TEXT length and returning InvalidColumnType on failed std::str::from_utf8. NULL, integer, real and BLOB retain their storage types; empty TEXT/BLOB and Unicode remain supported.
Our downstream regressions cover embedded NUL/Unicode, empty values, malformed UTF-8 refusal, detached owned values and subsequent connection reuse. This report is independent of the connection teardown issue #2251.
Proposed local-driver patch (the SQL engine is unchanged):
--- a/libsql/src/local/rows.rs
+++ b/libsql/src/local/rows.rs
@@ -127,8 +127,17 @@
}
pub fn get_value(&self, idx: i32) -> Result<Value> {
- let val = self.stmt.inner.column_value(idx);
- <crate::Value as FromValue>::from_sql(val)
+ // Use the exact byte length, preserve NULs,
+ // and reject invalid UTF-8 instead of truncating or panicking.
+ match self.get_ref(idx)? {
+ ValueRef::Null => Ok(Value::Null),
+ ValueRef::Integer(n) => Ok(Value::Integer(n)),
+ ValueRef::Real(n) => Ok(Value::Real(n)),
+ ValueRef::Text(bytes) => std::str::from_utf8(bytes)
+ .map(|text| Value::Text(text.to_owned()))
+ .map_err(|_| Error::InvalidColumnType),
+ ValueRef::Blob(bytes) => Ok(Value::Blob(bytes.to_vec())),
+ }
}
pub fn column_type(&self, idx: i32) -> Result<ValueType> {
- Lenguaje dominante
- C
- Estrellas
- 17.3k
- Forks
- 545
- Métricas de merge de PR
- Sin PR fusionados en 30 d
Preparar el entorno
- Incluye un Dockerfile o un archivo de Docker Compose
- Sin plantilla de pull request
- Leer la guía de contribución
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de tursodatabase/libsql
-
libsql-ffi: broken builds under Nix on macOSPosiblemente ocupada @frectonz la tomó hace 59 días. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 86/100
tursodatabase/libsql#2275 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
tursodatabase/libsql#2226 ·
-
Dificultad 1/5 Menos de una hora Aptitud para principiantes 65/100
tursodatabase/libsql#1925 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 62/100
tursodatabase/libsql#1892 ·
-
Rust: final update-hook closure and captures leak on connection closePosiblemente ocupada @atontini la tomó hace 3 días. Abierto
Dificultad 4/5 3-5 días Aptitud para principiantes 58/100
tursodatabase/libsql#2295 ·
Todos los issues de tursodatabase/libsql
Issues similares
-
bug
Dificultad 2/5 1-3 horas Aptitud para principiantes 76/100
valkey-io/valkey#4979 · 1 reacción ·
Los mantenedores suelen responder en 2 días
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 70/100
colbymchenry/codegraph#2770 ·
Los mantenedores suelen responder en 1 día
-
area/ysql kind/enhancement priority/medium status/awaiting-triage
Dificultad 2/5 1-3 horas Aptitud para principiantes 73/100
yugabyte/yugabyte-db#35578 ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 66/100
Los mantenedores suelen responder en 1 día
-
test_announce_and_connect_via_dns binds a fixed port that other tests' nodes can be handedPosiblemente ocupada @ksedgwic la tomó hoy. Abierto
Dificultad 2/5 1-3 horas Aptitud para principiantes 83/100
ElementsProject/lightning#9642 ·
Los mantenedores suelen responder en 2 días