The archive/CVE-2025-3277
sqlite/sqliteDisclosed 2025

CVE-2025-3277:sqlite: Integer overflow

A historical CVE, retraced through the questions Lachesis asks of vulnerable code.

Historical recordunspecified
WeaknessCWE-190 → CWE-787 → CWE-120 · Integer overflow
ImpactA integer overflow in sqlite, reachable from attacker-controlled input.
Read the reconstruction
01What happened

SQLite allocated space using a size that had already wrapped, then copied the full result beyond that smaller buffer.

That is the shape of the failure. The rest of this case file follows the evidence behind it.

Separate the record from the run

What was known.
What Lachesis established.

01Historical record

Known before Lachesis ran

The CVE and vulnerable release were selected from public history. We are not claiming novel discovery.

CVE
CVE-2025-3277
Vulnerable target
vulnerable build
Prior knowledge
CVE-2025-3277 · unspecified is vulnerable
then
02Lachesis run
Verified captured run

Independent re-detection

The experiment graph was built around the vulnerable function bodies concatFuncCore, concatFunc, and concatwsFunc (from the affected revision), rather than starting from arbitrary SQLite source. We did not point the tool at the CVE. Enumeration ran over the whole candidate registry (every family), and Lachesis surfaced the string-concatenation memory sink on its own. The verdict and every field under sast_output is the enumerator's own emission; everything under adjudication is my reading of the source and fix diff and is labelled as such.

Seeded inputs
None
Run timestamp
2026-08-24T12:06:09Z
Evidence artifact
~/.lachesis/graphs/sqlite_concat.kuzu

The commands and outputs below come from this recorded Lachesis run.

The reconstruction, in one frame

What Lachesis reconstructed.

One historical repository. Four captured queries. A complete source-to-sink argument.

Lachesis Casefiles · Unbound ComputeVerified runCVE-2025-3277
A wrapped size made SQLite's result buffer too small for the copy.
01Sourceint nSep = sqlite3_value_bytes(argv[0]); // concatwsFunc: length of attacker's separator
02Sinkmemcpy(&z[j], zSep, nSep); // separator copy
03Missing guardn += (argc-1)*nSep;
$ lachesis sources_of → reaches → guard_dominance
sqlite/sqlite · vulnerable buildunboundcompute.com
This was known history. Your code is not.

Lachesis follows the same evidence path through unfamiliar repositories.

The Lachesis reconstruction

Here is how the path becomes visible.

These are the recorded questions Lachesis asked of the historical vulnerable code, followed by the raw result and source location each query returned.

Repositorysqlite/sqlite
Revisionvulnerable build
EngineLachesis

Captured runThis block is a real, replayed run of Lachesis over the graph we built for this case. Every command below was executed against ~/.lachesis/graphs/sqlite_concat.kuzu and every result is the output captured on that drive. We did not tell the tool where to look; the hunt enumerated the whole taxonomy first and the sink fell out of that. The mode is independent-redetection: no attacker input string or payload was seeded, the graph was built from the source alone and the finding was rediscovered from graph structure.

01 · load_graph01 / 05
Lachesis ran
load_graph --path ~/.lachesis/graphs/sqlite_concat.kuzu --profile all && candidate_census

Load the graph and list every bug family

First we load the graph and ask the tool to list every bug family it knows about, with no family chosen ahead of time. It reports all 8 top level domains and 31 sink constructors and says the census is complete for what the graph can observe. This is the blind starting point: the memory copy family is just one row among all of them, not something we pointed the tool at.

Captured resultCaptured · load_graph
raw captured outputload_graph
[lachesis-mcp] loaded the graph; overlay: 116 derived edges; dataflow tier: present
{"move": "load_graph", "graph": "~/.lachesis/graphs/sqlite_concat.kuzu", "profile": "all"}

CANDIDATE_CENSUS
move: candidate_census
taxonomy (8):
  domain=lifecycle   title=Resource lifecycle       enumerable=True
  domain=memory      title=Memory safety            enumerable=True  families=[copy(memory.copy.capacity), index(memory.index.capacity), alloc(memory.alloc.size)]
  domain=injection   title=Injection                enumerable=True
  domain=navigation  title=Request forgery & redirection  enumerable=True
  domain=object-integrity  title=Object integrity   enumerable=True
  domain=filesystem  title=Filesystem               enumerable=True
  domain=crypto-config  title=Cryptography & transport config  enumerable=True
  domain=resource    title=Resource exhaustion      enumerable=True
constructors (31): complete_for_observable_graph=True
applied: True
atropos c bind: {callsites: 12, bound: 3, symbol-not-found: 258}, role_nodes: {sink: 4}
src/func.cline 0
Code location returned for this evidence step.
The repair

A small line with a large consequence.

The 32-bit multiplication overflows before the i64 widening. No overflow check gates the allocation size.

Before
n += (argc-1)*nSep;
After the patch
// n += (argc-1)*(i64)nSep;  (fixed in 3.49.1, commit f4fc2ee)
Sources & further reading

The original record.

Read the historical advisory and vulnerability record behind this reconstruction.

Open the Lachesis quickstart