blog.rust.gay

Bundled SQLite in libsqlite3-sys could leak uninitialized memory through crafted WAL files

RustSec published advisory RUSTSEC-2026-0459 on November 9, 2026 for libsqlite3-sys, the low-level crate that many Rust SQLite bindings use to compile or link SQLite. The issue is not a memory-safety bug in Rust code. It is an upstream SQLite vulnerability, tracked as CVE-2026-119375, that becomes relevant to Rust users when their dependency graph builds SQLite from bundled C sources. In the affected case, opening a database file together with a sibling write-ahead log (-wal) whose page size differs from the database can cause SQLite to return uninitialized page-cache memory. According to the RustSec advisory, a crafted pair of files can make ordinary queries return stale bytes from the process's memory.

That distinction matters for triage. A Rust application can be memory safe in its own code and still inherit a C library vulnerability through a bundled dependency. It also means the fix is not the same for every build: some projects need a crate upgrade, some need to audit Cargo features, and some need to check which SQLite library their binaries actually link at build or runtime. The advisory gives enough detail to make those decisions without guessing, and it is worth reading as a case study in how native dependencies flow through the Rust ecosystem.

What the underlying SQLite bug does

The RustSec description is narrow and concrete. SQLite versions before 3.54.0 can return uninitialized page-cache memory when a process opens a database file with a sibling -wal file whose page size differs from the database's. The advisory states that a crafted pair of files can make ordinary queries return stale bytes from the process's memory. The attack vector is local, and the victim process must open the files.

SQLite's 3.54.0 release log shows that version 3.54.0 was released on 2026-10-09. The RustSec advisory links the upstream fix to SQLite bug report 2026-08-17T15:54:41Z and commit e5bf119821f25be1d7f10ef0a2edbe175289166e. Those references establish the boundary between affected and fixed upstream behavior: versions before 3.54.0 are in scope for the issue as described, and the patched SQLite line appears in the 3.54.x releases.

The bug is best understood as an information-disclosure problem triggered by file parsing/state mismatch, not as remote code execution. CVSS 4.0, as reported by RustSec, scores it 6.8 (Medium) with attack vector Local, attack complexity Low, attack requirements None, privileges required None, and passive user interaction. The vulnerable-system impact is high confidentiality, with no integrity or availability impact reported in that vector. In plain terms, the risk is that a process may read back bytes it should not have been given when it opens attacker-controlled database artifacts. That is serious for applications that process untrusted or semi-trusted SQLite files, but it is not the same threat model as a network-exposed parser that can be triggered without local file access.

The write-ahead log detail is important. SQLite's WAL mode uses a -wal sidecar file next to the main database file. The vulnerability does not require a malformed SQL statement in the usual sense; it arises from the combination of database and WAL state. That means defenses that focus only on SQL input are not sufficient. If an application accepts a .db file from elsewhere, it also needs to think about adjacent files, archive extraction behavior, temporary directories, and whether an attacker can place or influence a -wal file that will be opened alongside the database.

How libsqlite3-sys becomes the Rust entry point

libsqlite3-sys provides raw bindings and build logic around SQLite. Higher-level crates such as rusqlite commonly depend on it. The security exposure depends on how SQLite is supplied. RustSec explains that libsqlite3-sys bundles SQLite when it is built from its bundled source. The bundled feature selects that mode, and rusqlite's bundled feature enables it. When a project compiles that bundled copy, the vulnerable SQLite code is compiled into the build unless the bundled version has been updated.

This is why the advisory is filed against libsqlite3-sys even though the root cause is in SQLite's C implementation. From a dependency-management perspective, Rust users often consume SQLite through crate features rather than by directly vendoring or linking sqlite3.c. A Cargo.toml change, a feature flag inherited through a workspace member, or a transitive dependency can determine whether the final binary contains the old bundled SQLite. Treating the issue as "only a C bug" would miss that practical path.

The same build logic creates an important non-affected case. RustSec states that a build that links a system SQLite instead of the bundled source is not affected by the copy in this crate; its exposure is that of the system library's version. That is good news only if you have verified the system library. It is easy to assume "system SQLite" means "patched SQLite," especially on developer machines with recent packages, while production images, embedded targets, minimal containers, or statically linked artifacts may tell a different story. The right question is: which SQLite implementation is present in the artifact I ship, and what version is it?

Affected and patched versions

RustSec's version guidance is specific. Releases 0.6.0 to 0.38.2 of libsqlite3-sys bundle SQLite 3.13.0 to 3.53.2 and are affected. Earlier releases contain no SQLite source, which is why they are listed as unaffected in this context. libsqlite3-sys was updated to bundle patched SQLite in 0.39.0 (SQLite 3.54.0) and 0.39.1 (SQLite 3.54.1). Those updates were released with rusqlite 0.41.

The rusqlite 0.41.0 release notes include "Bump bundled SQLite version to 3.54.0" among other changes. That aligns with RustSec's mapping, but teams should still confirm the resolved dependency versions in their own lockfiles. A direct dependency on rusqlite may not be enough if another crate pins libsqlite3-sys, if a workspace uses multiple SQLite wrappers, or if feature resolution produces a different build than expected. cargo tree -i libsqlite3-sys and inspection of Cargo.lock are the fastest ways to see which versions are actually in play.

The upgrade path also has toolchain implications. RustSec notes that libsqlite3-sys 0.39 declares Rust 1.88 and rusqlite 0.41 declares Rust 1.89. That does not change the security facts, but it affects rollout planning. Teams with pinned Rust toolchains, older CI images, or conservative MSRV policies need to account for the compiler requirement while scheduling the update. For security fixes, it is usually better to make the toolchain move explicit than to silently remain on a vulnerable bundled SQLite because an upgrade looked inconvenient.

The SQLCipher caveat

One of the most useful details in the advisory is easy to miss: the bundled-sqlcipher feature is not automatically covered by the same fix level. RustSec states that bundled-sqlcipher builds SQLCipher instead of SQLite. In libsqlite3-sys 0.39.0 and 0.39.1 it depends on sqlcipher-src =419.0.6, which bundles SQLite 3.53.4 and is affected.

This matters for projects that chose SQLCipher for encryption and may assume that upgrading libsqlite3-sys resolves every bundled-database exposure. It does not, at least for the versions named in the advisory. The right response is to treat SQLCipher-backed bundled builds as a separate verification target. Check the actual SQLCipher/SQLite base version your dependency resolves, follow upstream SQLCipher and sqlcipher-src updates, and avoid declaring the issue fixed until the bundled code path you use includes the upstream correction.

The broader lesson is that feature flags can fork your security posture. bundled, system linking, and bundled-sqlcipher are not cosmetic options; they select different native code inputs. Security reviews should record which path is used in development, CI, and release builds, because those environments can diverge.

Who should prioritize this fix

The advisory gives a clear exposure condition: only a process that opens a database file, with its -wal, that a less trusted party could have written is exposed. That points to a practical priority model.

High-priority cases include applications that import SQLite databases from users, customers, partner systems, email attachments, browser downloads, sync folders, or untrusted archives. Desktop tools, forensic utilities, data migration pipelines, backup importers, and mobile or edge agents that receive database snapshots can all fit this pattern. If the process has handled secrets, tokens, decrypted material, or other sensitive data in memory, the confidentiality impact becomes more concerning because "stale bytes from the process's memory" could include data unrelated to the database being opened.

Lower-priority cases are still worth fixing, but the immediate blast radius may be smaller. A service that creates its own database files in a directory writable only by the same trusted service account, never accepts WAL sidecars from outside, and never opens database artifacts from less trusted sources has a narrower local attack path. Even there, "local" does not mean irrelevant: multi-tenant hosts, compromised CI jobs, shared volumes, backup restore paths, and archive extraction bugs can all turn file placement into a security issue. The fix remains straightforward enough that deferring it should be a conscious risk decision, not an accident.

Remediation steps for Rust projects

Start by determining whether you bundle SQLite. Look for libsqlite3-sys features such as bundled, and for rusqlite features that enable bundling. If you intentionally use the system library, identify the package or library version in every environment that matters. RustSec's point is simple but decisive: the crate's bundled copy is one exposure path; a system-linked build has the system library's exposure.

If you use bundled libsqlite3-sys, upgrade to a patched release line. RustSec identifies >=0.39.0 as patched for the bundled SQLite issue described in the advisory, with 0.39.0 and 0.39.1 bundling SQLite 3.54.0 and 3.54.1 respectively. In practice, that means moving the relevant dependency graph to versions released with rusqlite 0.41 or newer compatible releases that carry the patched libsqlite3-sys. After upgrading, regenerate or update Cargo.lock as appropriate, rebuild release artifacts, and verify with cargo tree that old vulnerable versions are no longer selected.

If you use bundled-sqlcipher, do not stop at the libsqlite3-sys version check. RustSec explicitly warns that the SQLCipher path in 0.39.0 and 0.39.1 depends on sqlcipher-src =419.0.6 bundling SQLite 3.53.4 and is affected. Treat that as unresolved for the affected bundled-sqlcipher configuration named by the advisory until you have evidence from upstream dependencies or release notes that the SQLCipher bundle includes the fix.

For applications that must open externally supplied databases before they can be fully patched, consider defense-in-depth measures that match the advisory's conditions. Open untrusted database files in an isolated working directory. Do not copy or accept adjacent -wal or -shm files from untrusted sources unless your workflow specifically requires them and you have validated their provenance. Run import/parsing tasks with least privilege and minimal access to sensitive process memory where feasible. These measures reduce exposure; they are not substitutes for using a fixed SQLite version.

Why this advisory is a useful supply-chain reminder

Rust's package ecosystem makes it easy to compose high-level libraries, but native dependencies still cross language boundaries. A crate version number, a Cargo feature, and a bundled C source archive can together determine whether a vulnerability is present. RUSTSEC-2026-0459 is a good example because it requires several precise questions: Is SQLite bundled or linked from the system? Which libsqlite3-sys version is resolved? Which SQLite version does that build contain? Is SQLCipher involved? Can the process open a database plus a -wal file controlled by a less trusted party?

Those questions are more useful than a generic "update SQLite" instruction. They also show why RustSec advisories for -sys crates deserve attention even from teams that mostly think in safe Rust. The vulnerable code path here is in SQLite, but the packaging and delivery path runs through Rust build features and dependency resolution.

The issue also highlights the value of authoritative dates and version boundaries. RustSec reported the issue on October 30, 2026 and issued the advisory on November 9, 2026. SQLite 3.54.0 shipped on 2026-10-09 with the fix lineage referenced from the advisory. libsqlite3-sys 0.39.x and rusqlite 0.41 provide the patched bundled path for the standard bundled feature, while the SQLCipher variant needs separate scrutiny. With that information, maintainers can make a concrete plan instead of relying on assumptions.

Conclusion

RUSTSEC-2026-0459 is a targeted but important reminder that bundled native libraries are part of a Rust application's security surface. The bug itself lives in SQLite before 3.54.0: a mismatched -wal page size can cause ordinary queries to return uninitialized page-cache memory when a crafted database/WAL pair is opened. For Rust users, the exposure appears through libsqlite3-sys when the bundled SQLite source is compiled in, including the common rusqlite bundled configuration.

The response is mostly straightforward: confirm whether you bundle SQLite, upgrade to patched bundled releases, account for the Rust 1.88/1.89 toolchain requirements noted by RustSec, and treat bundled-sqlcipher as a separate case because the advisory reports it as affected in the named 0.39.x builds. If your software opens SQLite files from less trusted sources, prioritize the fix and reduce exposure to attacker-controlled sidecar files in the meantime. The facts are well documented in the RustSec advisory and upstream SQLite references, which makes this an issue teams can assess quickly and remediate deliberately.

Sources


← back to posts