Skip to content

Backport the security fixes to v0.4 for a 0.4.25 release#707

Open
hsbt wants to merge 20 commits into
v0.4-stablefrom
backport/v0.4/security-patches
Open

Backport the security fixes to v0.4 for a 0.4.25 release#707
hsbt wants to merge 20 commits into
v0.4-stablefrom
backport/v0.4/security-patches

Conversation

@hsbt

@hsbt hsbt commented Jun 15, 2026

Copy link
Copy Markdown
Member

Ruby 3.3 bundles net-imap from the 0.4.x line, which still has no fixed release for the three advisories published on 2026-06-09 (CVE-2026-47240, CVE-2026-47241, CVE-2026-47242). They are fixed in 0.5.15 and 0.6.4.1, but the latest 0.4 release is 0.4.24.

As the Ruby 3.3 maintainer I would like a release that carries the behavior changes required by the security fixes, while staying free of the other 0.5.x incompatibilities. Moving 3.3 from 0.4 to 0.5 in a patch release would pull in SequenceSet, the MessageSet deprecation, the config object, and a higher required_ruby_version, none of which belong in a maintenance update.

So I went through v0.5.14..v0.5.15 and backported the equivalent commits onto v0.4-stable, mirroring your 0.5.15 backport in #703. I kept only the security fixes and the prerequisites they depend on, adapted them to the 0.4 code (which still targets Ruby 2.7.3 and uses MessageSet rather than SequenceSet), and left out everything 0.5-only. The full suite is green, and there are no breaking public API changes beyond the intended rejection of the malicious inputs.

How do you feel about cutting 0.4.25 from this branch? If it helps, I am happy to handle the release work myself.

nevans added 17 commits June 15, 2026 14:14
`Atom` and `Flag` have only been used for argument validation since
v0.6.4 (as well as v0.5.14 and v0.4.24), and they validated for absense
of `atom-specials`.  But they failed to check that the strings are not
empty.

While this could be used to create syntax errors, I don't believe it
amounts a security vulnerability.  The result would be no different from
any other `BAD` server response, which an application must be prepared
to handle.
Previously, integer arguments had to be 32-bit unsigned, and a special
case exception was made for the number in a `["CHANGEDSINCE", number]`
array.  But several other command arguments can be larger:
`UNCHANGEDSINCE`, quota `resource-limit`, and `tagged-ext-simple`.

So, generic Integer arguments should allow 64 bit numbers.  We can add
specific argument validation where it makes sense to do so.
This guards against numbers like `99999999999999999999`.

This isn't a security issue unless `max_response_size` is `nil`.  But
it's reasonable to block too large numbers in both the response reader
and the response parser, regardless of that config setting.

Note also that this still _allows_ strings like `000000000000000001`.
This is goofy, but it's how the RFCs are written!

----

🍒 pick note: `NumValidator` in v0.5 doesn't have `coerce_number64`.
Rather than backport _that_, I opted to simply copy a simplified version
into both `ResponseReader` and `ResponseParser`.
This lets us test some specific error conditions, without failing
because the server is upset.
It's convenient that `RawData` can take a string, even though it's
composed of parts.  But it's surprising, IMO, to _require_ the `data`
parameter be a string, when the `data` member is an array.
`#disconnect` could deadlock when called inside the mutex, because the
receiver thread could be signaling a condition variable and it would
thus be unable to resume until the current thread releases its lock.

Also, `#disconnect` shouldn't raise `IO#shutdown` exceptions on the
receiver thread prior to closing the socket, when it is being called
from the receiver thread.  The exception is still raised, _after_ the
socket is closed.
This shares most of the validation implementation with RawText, via an
extracted shared superclass.

Please note: in currently released `net-imap`, QuotedString is _not_
used to quote regular string arguments.  It's currently only used by
`Net::IMAP#id` (the `ID` extension).

Without this patch, `Net::IMAP#id` is vulnerable to the same CRLF
injection issues in GHSA-75xq-5h9v-w6px and GHSA-hm49-wcqc-g2xg.  The
string will be quoted, which may limit the ability to inject some
commands.  Presumably, `Net::IMAP#id` is unlikely to be called with
user-provided input, making this less likely to be exploitable.
Nevertheless, CRLF injection should be prevented!
This still allows the argument to be a single string with multiple
space-delimited arguments.  It splits the string first, and validates
the substrings.
Zero-length literals are explicitly allowed by the RFCs and this did not
catch text that ends with `{0}` or `{0+}`.  This leaves RawData able to
absorb the `CRLF` that ends the command, and thus absorb the following
command into itself.

Ultimately, we don't care if the `number64` is encoded correctly nor
whether it claims to be a binary literal.  So I've simplified the regexp
by dropping `~?` and using `\d+` for the number.
It's bad behavior to send a non-synchronizing literal that's too large.
This forces the server to choose between reading but ignoring the bytes
or closing the connection.

But, for servers that _don't_ support non-synchronizing literals, this
could be another CRLF/command injection attack vector.  If a server sees
the `}\r\n` but can't parse the literal bytesize, it may decide to close
the connection, and all is fine.  But, a server _might_ respond to any
unparseable command line (ending in `CRLF`) with `BAD`, then interpret
the literal as the next command.  In that case, a CRLF/command injection
could succeed.

Fortunately, `LITERAL-` is supported by most IMAP servers.  So this
is not expected to be widely exploitable.
These tests can raise `Errno::ECONNREST, "Connection reset"` in the
server thread.  I've only seen it in TruffleRuby (semi-reliably) and
MacOS (flaky), but probably it's a timing issue and can happen elsewhere
too?
@hsbt hsbt changed the title Backport/v0.4/security patches Backport the security fixes to v0.4 for a 0.4.25 release Jun 15, 2026
The non-synchronizing literal tests from the #701 backport used endless
method definitions, which require Ruby 3.0.  The v0.4 line still supports
Ruby 2.7.3, so this broke CI on 2.7.
@hsbt hsbt force-pushed the backport/v0.4/security-patches branch from e90f822 to 36489fb Compare June 15, 2026 06:58
hsbt added 2 commits June 15, 2026 16:05
When a client closes the socket abruptly, the server thread's `run`
ensure and the test helper's `shutdown` can call `close` concurrently.
Without a mutex the `state.logout?` check races, so both callers run
`state.logout` and the second raises "already logged out", flaking the
send_literal tests on ubuntu CI.
@hsbt hsbt force-pushed the backport/v0.4/security-patches branch from 36489fb to 8a238ad Compare June 15, 2026 07:08
@nevans

nevans commented Jun 21, 2026

Copy link
Copy Markdown
Collaborator

@hsbt Sorry for the slow reply. I've been on family vacation, without any cell signal!

I was using a "critical security fixes only" approach to v0.4.x ever since the last release, so I had intentionally decided to not backport those (if only I'd caught them a few weeks earlier).

The CVSS 4.0 scores should not mark these as critical, and even those scores are somewhat inflated, IMO.

My cell signal right now is very temporary (only a few minutes), and I'm going to be without cell signal again for a few days. So I won't have time to review until then. I'll trust your judgement, if you think they should be merged and released.

@hsbt

hsbt commented Jun 22, 2026

Copy link
Copy Markdown
Member Author

@nevans 👋 No worries about the delay at all. Let me think it over on my side. For now, please forget about OSS and enjoy the rest of your family vacation.

@junaruga

junaruga commented Jun 29, 2026

Copy link
Copy Markdown
Member

I was using a "critical security fixes only" approach to v0.4.x ever since the last release, so I had intentionally decided to not backport those (if only I'd caught them a few weeks earlier).

@nevans I want to ask you to backport these CVE-2026-47240, CVE-2026-47241, CVE-2026-47242 to net-imap 0.4.x too. Because I think some users running critical system still may want us to fix these CVEs with low CVSS score in Ruby 3.3.

@junaruga

junaruga commented Jun 29, 2026

Copy link
Copy Markdown
Member

My cell signal right now is very temporary (only a few minutes), and I'm going to be without cell signal again for a few days. So I won't have time to review until then. I'll trust your judgement, if you think they should be merged and released.

Sorry I misunderstood this was an issue ticket. But this is PR ticket. So, my ask is if you have CVEs with low CVSS in the future, I want you to consider backporting the fix to net-imap versions (in this case, including 0.4.x) to be included in every supported Rubies too, because some users running critical system may want us to fix these CVEs.

@junaruga

Copy link
Copy Markdown
Member

Nowadays, zero-CVE container images are used in some use cases. I think that means some users prioritize unfixed CVE number in some use cases rather than CVSS score of CVE. The following article describes this topic as a reference.

Zero CVE Container Images - safeguard.sh
https://safeguard.sh/concepts/zero-cve-images

@nevans

nevans commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

@hsbt I think my original intention was to mirror the ruby release policy for the version of ruby that bundles that version of net-imap. But I see now that ruby 3.3 is simply marked as "security maintenance" without any reference to severity. At some point, I must have incorrectly remembered that the oldest supported ruby only supported critical security patches.

@nevans

nevans commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

@hsbt I'd previously skipped backporting several more severe security fixes to v0.3, for the same reason. The related CVEs (and the final net-imap v0.3) were released shortly after ruby 3.2's final release and EOL, but only because I took too long preparing them.

Another user asked for the other fixes to be backported to v0.3 and I described my decision to not backport all of them here: #663 (comment). (Sorry, my tone there was a bit grumpy.)

I would prefer not simultaneously maintain four versions of net-imap (once 0.7 is released). Since the minimum ruby version is only bumped for x.y releases, that effectively requires supporting seven versions of ruby.

@nevans

nevans commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

@junaruga I think it's completely reasonable to include a "critical security fixes only" release series, as a way of pushing users towards upgrading. If users want to maintain zero CVEs, they still get two major versions with full security support. Since net-imap releases x.y.0 before Dec 25th (when ruby releases its x.y.0), that's over two years. In my opinion, offering more than two years of full security support should rely on a staggered LTS approach, similar to Ubuntu.

@nevans

nevans commented Jun 29, 2026

Copy link
Copy Markdown
Collaborator

But, my original goal was to approximately match ruby's release policy, and I was wrong about what that policy is! So I suppose we'll need to release these backports. I'll take a closer look at merging and releasing this PR tomorrow.

@nevans

nevans commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

This is also somewhat complicated because most of the CVEs have expanded the scope of net-imap's threat model: net-imap was originally written for use with trusted inputs to access trusted IMAP servers. That's a normal use-case for an email client library. From a normal email user's point of view, if attackers already control your email server or your email client inputs, then you've already lost everything. This can be a reasonable threat model... if it's well documented.

But this was not well documented, so some users probably have used it more naively, in use cases that require a different threat model.

All of net-imap's CVEs have been for either 1) untrusted command arguments, 2) untrusted servers, or 3) untrusted connection (STARTTLS stripping!). net-imap didn't really do any significant argument validation, and it usually trusts that the server's responses are not malicious. To a certain extent, this is still the underlying assumption, and net-imap is not even attempting to protect you from a variety of exceptions. If you naively connect to an untrusted server with TLS, that server can still do all sorts of things to confuse or DoS net-imap. If you naively use untrusted user input for search or fetch (etc), other sorts of argument injection are still possible.

When I first started using net-imap for scenarios outside its original threat model, I knew I was going outside the safe use cases. So I validated all user inputs prior to using them as Net::IMAP command arguments. And I added many extra safeguards for untrusted (or buggy) servers: handling bizarre error conditions and mitigating against DoS by timeout or memory consumption. And I've never supported STARTTLS (only port 993 with implicit TLS). As a result (as far as I know), none of the CVEs ever affected my production software any worse than your average server bug from iCloud or Yahoo or Outlook.

Because net-imap was originally built for that simpler threat model, we could potentially end up needing bigger and bigger changes to support older versions that are used by almost no one, that are very different from any version I've ever used in production myself, that I wouldn't recommend for anyone else to ever use in production.

So, if your software is in that smaller set of use-cases where you are affected by these CVEs, please just upgrade!

@junaruga

Copy link
Copy Markdown
Member

@junaruga I think it's completely reasonable to include a "critical security fixes only" release series, as a way of pushing users towards upgrading. If users want to maintain zero CVEs, they still get two major versions with full security support. Since net-imap releases x.y.0 before Dec 25th (when ruby releases its x.y.0), that's over two years. In my opinion, offering more than two years of full security support should rely on a staggered LTS approach, similar to Ubuntu.

@nevans Thanks for the explanation. Yes, I understand your initial decision backporting critical security fixes only is reasonable. As my information, I am working for Red Hat Enterprise Linux (RHEL) at Red Hat, and I am familiar with such use cases changing actions (fix or not fix) depending on the severity (such as CVSS) of CVEs in RHEL.

To be fair, even if you decide not to backport the low CVSS CVE fixes to any stable branches, that is also reasonable (it has a reason). I think most different decisions are reasonable from different perspectives, but a factor to differentiate a decision is which reason is more prioritized than other reasons.

I am glad to see that you said that you will work on backporting the low CVSS CVE fixes to net-imap 0.4.x, by following Ruby project's release policy. Thanks for your work.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants