April 28, 2025·4 min read·engineeringrubysecurityimapmemory

net-imap bounds server-controlled response allocation

CVE-2025-43857 lets a malicious IMAP server trigger a large client allocation with a literal length. The fix adds response limits, while retained response counts remain an application responsibility.

A server does not need to send a huge message to make an affected net-imap client allocate space for one. It can send the literal byte count that promises such a message. The client's receiver then passes that count to IO#read, which allocates the buffer before the rest of the response has arrived. A short protocol declaration can therefore cause a much larger local memory commitment.

Ruby's April 28 advisory assigns this problem CVE-2025-43857. The more detailed net-imap advisory explains the allocation path and configuration changes. Corrected releases add a maximum response size, but compatibility decisions mean some supported branches retain an unlimited default.

I find Ruby's straightforward IO interfaces pleasant to read, but their allocation behavior remains part of the program's design. A length supplied by another machine is a request for local resources. This fix makes that request subject to an explicit response limit instead of treating the remote count as sufficient authority to allocate.

Literal syntax carries an allocation request

IMAP permits literal strings in responses. Their prefix contains a byte count in braces followed by a line ending. The client uses that count to determine how many bytes belong to the literal rather than treating its contents as ordinary protocol delimiters.

The vulnerable path automatically runs in the receiver thread whenever a connected server supplies such a response. It calls IO#read with the announced size. The advisory says no further data is necessary to cause the allocation. The mismatch between bytes received and memory reserved is what makes the attack economical for the remote side.

This is an allocation-before-validation problem at a protocol boundary. Waiting to inspect the complete literal would be too late if the buffer has already consumed the memory. The response reader must apply its resource policy while interpreting the prefix, before trusting the announced length as a buffer size.

The advisory distinguishes trusted, well-behaved servers reached securely from insecure connections and buggy, untrusted, or compromised servers. A user-supplied hostname is one example of the latter exposure. The trust assumption belongs to the remote endpoint as well as the transport used to reach it.

Corrected versions add a configurable ceiling

The published corrected releases are net-imap 0.2.5, 0.3.9, 0.4.20, and 0.5.7. The Ruby announcement identifies the corresponding affected intervals, ending at 0.2.4, 0.3.8, 0.4.19, and 0.5.6. These are gem versions, so the interpreter's own patch number is not a complete description of the code performing the read.

The new setting is max_response_size. It constrains the response reader rather than changing the server's protocol. A client can set it when constructing a connection. For example, the advisory demonstrates a deliberately small per-response value for an environment where that size fits the intended operations:

require "net/imap"

imap = Net::IMAP.new(
  hostname,
  ssl: true,
  max_response_size: 16 << 10
)

Here 16 << 10 is 16 KiB. It illustrates the constructor option, not a universal size suitable for every mailbox operation. A client fetching complete messages can legitimately require more than a client retrieving small metadata responses.

The advisory also documents changing the setting on an existing connection. It uses noop afterward to flush a waiting read so the new setting takes effect in that receiver flow. That detail matters when modifying a live connection instead of selecting the policy at construction.

Defaults preserve compatibility on older branches

For net-imap 0.5.7, the documented default is 512 MiB. For the corrected 0.4, 0.3, and 0.2 lines, it is nil, meaning unlimited. The maintainers chose these defaults to avoid breaking existing secure connections to trusted servers with large responses.

Consequently, upgrading and choosing a suitable limit are separate actions for untrusted endpoints. An application on an older corrected branch can have the new protection available without enforcing any ceiling until it configures one. Reporting the patch as an automatic tight memory bound would misrepresent those defaults.

A limit also changes failure behavior for legitimate oversized responses. The advisory says such commands can raise an exception and close the connection. It recommends paginated or limited command forms where possible, including fetching message bodies in chunks. Raising the ceiling can permit a particular operation, but it increases the memory commitment accepted from that server.

This tradeoff follows the unit of enforcement. A per-response ceiling must accommodate the largest response the application intends to receive as one unit. Reducing that unit through bounded fetches can preserve functionality without allowing a whole large message to determine one allocation.

Response count remains a separate bound

max_response_size does not stop a flood of individually acceptable responses. It also does not limit the number of unhandled responses kept in the responses hash. The advisory explicitly leaves applications responsible for response handlers that prune excessive retained data.

These are different dimensions of memory consumption. One large response can exhaust memory through a single allocation. Many smaller responses can consume it through accumulation. A ceiling on the first does not establish a ceiling on the second, even when every response satisfies the new check.

The distinction also affects diagnosis. A client rejecting a single oversized literal has exercised the new response-reader protection. A client whose memory grows while retaining many small responses may never encounter that threshold. Both observations can be consistent with the patched implementation. The failing resource policy is located at a different stage, so treating every growth pattern as evidence that the literal-size check failed would obscure the actual cause.

I regard that distinction as the most useful part of the configuration guidance. The gem can enforce the size of a protocol unit, while the application understands which results it still needs to retain. Resource policy has to cover both stages of that lifecycle.

The fix therefore supplies a specific missing mechanism at the earliest allocation boundary. Corrected net-imap versions let the client reject an excessive announced response size. Applications connecting beyond a trusted server still need a value appropriate to their commands and handlers appropriate to their retained responses.