April 5, 2021·4 min read·engineeringrubysecurityxmlrexml

REXML 3.2.5 repairs XML structure changes during round trips

CVE-2021-28965 concerns crafted XML whose structure can change after parsing and serialization. REXML 3.2.5 tightens parsing and declaration handling, including matches that previously accepted data beyond the intended buffer position.

Parsing XML and writing it back out can look like a harmless normalization step. That assumption fails when a parser accepts a crafted input into a representation that serializes with different structure. Ruby's April 5 advisory for CVE-2021-28965 identifies this problem in REXML and recommends version 3.2.5 or later.

The advisory makes the impact conditional on how an application uses the parser. It does not claim that every REXML consumer suffers the same exploit. The underlying problem is still significant because code that checks one representation and later acts on another can depend on structural properties surviving the transformation.

I find Ruby's consistent interfaces helpful when following application code. A parse-and-write sequence is especially easy to read as a routine transformation. This vulnerability shows why the semantic contract of that sequence needs attention beyond the readability of the calls themselves.

Round-trip correctness concerns meaning

An XML round trip need not preserve every byte. A serializer can change formatting or quoting while retaining the intended document structure. The advisory describes a different failure, in which crafted XML produces a document whose structure differs from the original. That distinction is what makes a normal formatting comparison insufficient.

Consider a hypothetical pipeline that inspects an XML tree, serializes it and passes the result to another component. Its safety argument may assume that the checked structure is what the next component receives. If the transformation changes structure, the earlier check can lose its connection to the later action. Whether that creates a security issue depends on what the application checked and how it uses the result.

This is an analysis of the failure class, not a claim that every such pipeline is exploitable. The advisory explicitly leaves impact dependent on context. Understanding the distinction keeps a concrete parser defect from becoming an unsupported assertion about all XML applications.

The 3.2.5 release notes group the round-trip fixes under the vulnerability and credit Juho Nurminen's report. They also list other work in the release. The security update therefore has a specific documented purpose within a release that includes additional parser and documentation changes.

A line-start match could skip the intended position

One element-start correction replaces the caret anchor in the tag pattern with \A. The regression test supplies an extraneous less-than sign before a newline and an otherwise recognizable empty root element. The corrected parser raises a parse exception for the malformed start.

The important distinction is between the beginning of a line and the beginning of the input being examined. A pattern that can recognize a later line may skip material that should make the current parse position invalid. XML recognition needs to account for that material rather than silently finding a suitable tag farther along.

A related element-end correction makes the same anchoring change for closing tags. Its regression test puts malformed closing syntax before a newline and another closing tag. The expected behavior is a missing-end-tag parse error, rather than accepting a later line as if the intervening syntax did not matter.

These are small pattern changes with a meaningful parser consequence. Matching a familiar token somewhere in a buffer is not equivalent to recognizing the next valid token at the required position. The tests make that positional requirement explicit by surrounding recognizable syntax with malformed material.

Rejecting that malformed input is a compatibility change for any caller that previously relied on permissive acceptance. Such acceptance is precisely what the regression cases constrain. A parser error gives the caller a defined failure to handle, instead of producing a tree that hides the reason the original input was unsuitable.

Document state also controls what gets consumed

Another parser correction addresses data ignored before a document type declaration. It adjusts anchoring and the points at which the parser reads or consumes whitespace. It also sets the document status when entering an element, making later behavior depend on that explicit state.

That patch matters because a parser's correctness is distributed across recognition and consumption. A pattern may identify a token correctly while the surrounding state machine discards or reinterprets earlier data. The meaningful unit of review includes the buffer position before the match and the state after the token is consumed.

The regression material exercises processing-instruction content and expected parser events. It demonstrates that preserving content boundaries can require changes outside the code that ultimately serializes the document. A serializer cannot reconstruct distinctions that parsing has already discarded or represented incorrectly.

Updating the interpreter and updating the gem are different paths

The advisory identifies REXML 3.2.4 and earlier as affected. It lists Ruby 2.6.7, 2.7.3 and 3.0.1 as updated interpreter releases. For Ruby 2.6 and later, it also permits updating the gem independently. Bundler users can express the minimum corrected version in the Gemfile.

gem "rexml", ">= 3.2.5"

That constraint states which gem versions are acceptable to the bundle. It needs to be interpreted alongside the interpreter in use and the dependency resolution actually performed. The advisory gives a separate instruction for Ruby 2.5.8 and earlier, where a gem-only update is not the supported repair path. It directs those users to Ruby 2.5.9 and notes that the 2.5 series is at end of life.

I regard that distinction as necessary to a useful advisory. Naming a corrected library version is insufficient when some affected installations cannot adopt it through the same mechanism. The documented update route connects the parser correction to the way a particular Ruby installation supplies REXML.

The fixes also give a concrete definition of the behavior the update changes. Malformed token starts must not disappear because a later line contains recognizable syntax. Parsing and serialization must preserve the structural meaning on which downstream processing relies. Those are properties of the representation passed between components, even when each individual API call succeeds.