The archive/CVE-2025-6021
GNOME/libxml2Disclosed 2025-06-12

CVE-2025-6021:xmlBuildQName: the length math that wrapped

How Lachesis ranked the vulnerable copy path without being told its source, sink, or missing guard.

Historical recordv2.14.1
WeaknessCWE-190 → CWE-787 → CWE-122 · integer overflow → heap/stack buffer overflow
ImpactA crafted XML document with very long names can overflow a buffer and corrupt memory in any application built on libxml2, a core XML parser across Linux distributions and enterprise software.
Read the reconstruction
01What happened

Two attacker-controlled lengths wrapped during addition. The allocation became smaller, but the following copy kept its full size.

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-6021
Vulnerable target
v2.14.1 · vulnerable
Prior knowledge
CVE-2025-6021 · v2.14.1 is vulnerable
then
02Lachesis run
Verified captured run

Independent re-detection

no source/sink/guard fed. SAST candidate sweep across all enumerable families, ranked; then inspected which candidates the ranking floated to the top.

Seeded inputs
None
Run timestamp
2026-08-24T12:06:09Z
Evidence artifact
~/.lachesis/graphs/libxml2_cve_2025_6021.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-6021
The QName length wrapped before allocation, then memcpy copied the full value.
01Sourceattacker-controlled ncname / prefix lengths
02Sinkmemcpy(&ret[lenp + 1], ncname, lenn)
03Missing guardno overflow check on lenn + lenp + 2
$ lachesis sources_of → reaches → guard_dominance
GNOME/libxml2 · v2.14.1 · vulnerableunboundcompute.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.

RepositoryGNOME/libxml2
Revisionv2.14.1 · vulnerable
EngineLachesis

Captured runThis block reflects the real blind drive of the graph we built for libxml2. We loaded the whole graph, listed every bug family with nothing chosen ahead of time, and the overflowing copy came out ranked first of all the memory copy sinks. The candidate, model tag and rank below are the facts captured on that drive on that drive. No document was seeded; the finding was rediscovered from graph structure, so the mode is independent-redetection.

01 · load_graph01 / 05
Lachesis ran
load_graph --path ~/.lachesis/graphs/libxml2_cve_2025_6021.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, with nothing picked ahead of time. It reports all 8 domains and 31 sink constructors and says the census is complete for what the graph can observe. The memory copy family is one row among all of them.

Captured resultCaptured · load_graph
raw captured outputload_graph
{"move": "load_graph", "graph": "/Users/riyandhiman/.lachesis/graphs/libxml2_cve_2025_6021.kuzu", "profile": "all"}

CANDIDATE_CENSUS
move: candidate_census
taxonomy (8):
  domain=lifecycle         title=Resource lifecycle              enumerable=True
  domain=memory            title=Memory safety                   enumerable=True
  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
tree.cline 0
Code location returned for this evidence step.
The repair

A small line with a large consequence.

Nothing verifies that lenn + lenp + 2 has not wrapped before it is used as the allocation size. The later comparison is also an int and does not catch the wrap.

Before
ret = xmlMalloc(lenn + lenp + 2);
/* ... */
memcpy(&ret[lenp + 1], ncname, lenn);
After the patch
/* upstream fix: promote the size math to size_t and reject the wrap
   before allocating, so an oversized QName is refused rather than truncated. */
Sources & further reading

The original record.

Read the historical advisory and vulnerability record behind this reconstruction.

Open the Lachesis quickstart