#2257·libsql

Unsound api: row.next() invalidation not covered by lifetime and may* cause use-after-free

Author: CharaVerKysCreated Jul 13, 2026Updated Jul 13, 2026

synopsis:

rust
charaverk | ~/repos/libsql_execute/src ~$ cat main.rs 
fn main() {
    let runtime = tokio::runtime::Runtime::new().unwrap();
    runtime.block_on(async{
        let db = libsql::Builder::new_local("/tmp/test.db").build().await.unwrap();
        let conn = db.connect().unwrap();
/*
        conn.execute("CREATE TABLE IF NOT EXISTS test_table(id INTEGER PRIMARY KEY, data TEXT)", ()).await.unwrap();
        let _res = conn.execute(
            "INSERT INTO test_table(id, data) VALUES(0, ?1);
            ", [ "some str content" ]).await;
*/
        let mut res = conn.query(
            "SELECT data FROM test_table WHERE id == ?1;
            ", [ 0 ]).await.unwrap();
        let row = res.next().await.unwrap().unwrap();
        let str_ref = row.get_str(0).unwrap();
        let erm = res.next().await;
        //* unsound here, quickly debugged what cause, but still...
        let str_ = str_ref.to_owned(); //? invalidation that can lead to 'use after free' 
                                       //?   or just logical error?
        println!("{erm:?} {str_}");
    });
}

as in example, when from current row user get_str, it return non owning &str, and next call of rows.next() will cause invalidation of whatever was under reference str_ref

using previous row after call to next is already invalid, but it will not cause memory issues using &str to previous row, probably not cause memory issues now:

@sivukhin says:

for local client libsql/sqlite reuse memory for the columns between next() calls which makes this bug possible... I wouldn't rely on the reuse semantic too much. SQLite/libsql can free this memory after next() call too I think

so, seems like currently it can only lead to logical error, but would be better to somehow cover it, tho, i not know how to do so without allocating for each str