Ruby resolv limits the name produced by DNS decompression
CVE-2025-24294 exposes a missing bound on decompressed DNS names. The corrected decoder limits the expanded representation as well as checking packet positions and compression pointers.
A compressed network message has more than one relevant size. The bytes received can fit within a packet while the logical value reconstructed from them requires excessive work. Ruby's resolv vulnerability arises from checking the packet without sufficiently limiting the domain name produced during decompression.
The July 8 advisory assigns CVE-2025-24294 to this problem. A malicious DNS packet containing a highly compressed name can consume enough CPU to leave the application's thread unresponsive. The missing check concerns the resulting name length, so a packet-size bound alone cannot express the required invariant.
I find Ruby's readable interfaces useful, but a resolver is an example of substantial protocol work hidden behind a small call. The implementation must apply the protocol's constraints while it reconstructs values. A convenient return type cannot compensate for unbounded work performed before that value exists.
DNS compression reuses existing name data
RFC 1035 defines domain names as sequences of labels and describes compression using pointers to prior occurrences inside a message. Repeated suffixes need not be encoded again in full. A decoder follows the pointer and combines the referenced labels with any labels already read.
The compression pointer identifies an offset within the message. Its presence changes how the decoder traverses the input, since the next part of the logical name may be located elsewhere in the packet. The decoder also needs to remember where parsing should resume after completing that referenced name.
This is useful compression because DNS messages often contain related names. It also means sequential input length and reconstructed output length are different quantities. Reading only bytes that belong to the packet does not establish that the expanded name remains within its valid size.
The RFC's size constraints include a maximum domain-name length of 255 octets in its encoded label representation. Label-length bytes are part of that representation. A decoder therefore needs a counter that reflects the structure being reconstructed, rather than merely counting visible characters in a dotted presentation string.
Existing checks address different failure modes
The resolv 0.6.2 decoder shows several checks in the same loop. It rejects an input position beyond the current limit. It also rejects a compression pointer that does not move backward relative to the previous pointer position. Those checks remain relevant after the length fix.
A packet-position check prevents reading outside the available region. A backward-pointer rule constrains how references are followed. Neither one directly limits the total amount of label data collected for the decoded name. Each protects a different property of traversal.
The implementation records a saved position when following its first pointer. On completing the name, it returns to that saved position so the surrounding response can continue decoding. That separation between traversal and continuation is necessary because the referenced suffix occupies a different position from the field currently being parsed.
These details explain why decompression is not equivalent to splitting a string on dots. The parser tracks both the physical location of encoded labels and the logical sequence those labels form. A safety condition expressed in only one of those spaces can leave the other unconstrained.
The patch counts expanded label data
The fixed implementation adds a size accumulator as labels are appended. It accounts for each label's byte length and its length byte. When the accumulated size exceeds the bound, it raises a DecodeError instead of continuing to expand the name.
This places the check within the work-producing loop. The decoder does not wait until after an oversized result has been fully constructed to decide that it was invalid. It stops when the reconstructed representation crosses the supported limit.
That is the important resource property of the change. An end-of-operation validation can reject the same eventual value while still spending excessive CPU reaching it. An incremental check also constrains the amount of reconstruction the parser performs before rejection.
The error is a decoding failure for a name that exceeds the permitted representation. The fix does not redefine ordinary DNS compression or require valid senders to stop using it. It enforces a bound that the decompression path needed to preserve while interpreting compressed input.
The calculation uses byte sizes. That choice matches a wire-format limit expressed in octets, rather than depending on how a language string reports characters. Parsing a binary format requires its own units even when the implementation represents labels using ordinary string objects.
The affected component is the resolv gem
The advisory identifies resolv 0.2.2 and earlier in the Ruby 3.2 series, resolv 0.3.0 in Ruby 3.3, and resolv 0.6.1 and earlier in Ruby 3.4. It recommends upgrading the gem. Those component versions are the relevant inventory for this vulnerability.
Bundling a library with the interpreter can make the distinction easy to miss. The application uses a particular loaded library implementation. A statement that it runs on Ruby 3.3 alone does not identify every independently versioned component involved in resolving a name.
The 0.6.2 source provides a concrete view of the corrected loop. The advisory supplies the affected-version scope and impact. Reading them together avoids attributing a generic resolver problem to every possible Ruby DNS path without evidence. This disclosure concerns the named gem's decompression behavior.
It also identifies CPU exhaustion and an unresponsive application thread as the consequences. It does not describe credential disclosure or arbitrary code execution. Preserving that scope makes the explanation useful without inflating the security claim beyond the published evidence.
Expanded values need their own resource policy
The lesson for this parser is specific. Its traversal checks needed an accompanying limit on the name being constructed. A bound on input storage and a bound on decoded work are related, but one does not automatically imply the other when a format can refer back to existing data.
I regard the corrected loop as a useful example of placing an invariant where the cost occurs. Counting decoded label bytes is both a protocol validation and a resource bound. It stops the parser from doing work for a value that cannot be accepted.
The public API can continue returning ordinary resolved data for valid responses. The implementation change occurs underneath it, where compression pointers become labels. That is the boundary at which the fixed decoder now rejects an overlong name, before continuing through the remainder of a malicious expansion.