Fix infinite loop and memory exhaustion when folding RFC 2047 params - #32
Merged
alecpl merged 2 commits intoJul 28, 2026
Merged
Conversation
Contributor
|
I confirm the issue and the fix. However, with this change the attachment headers (from the test) look like this: RFC2047 says: " An 'encoded-word' may not be more than 75 characters long, including 'charset', 'encoding', 'encoded-text', and delimiters. If it is desirable to encode more text than will fit in an 'encoded-word' of 75 characters, multiple 'encoded-word's (separated by CRLF SPACE) may be used." So, we're not RFC compliant when the charset is that long. I would consider truncating the charset to a sane length (something like 48 or 64 characters). |
buildRFC2047Param() could loop until PHP ran out of memory when an encoded parameter value could not be folded within the wrap width: the quoted-printable branch left $_quote undefined and the base64 branch cut a zero-length chunk, so the value was never advanced. A single crafted header (e.g. an over-long charset name) was enough to trigger it. Break out of both loops when no further chunk can be extracted, and cap over-long charset names so every emitted encoded-word stays within the RFC 2047 75-character limit.
sebastka
force-pushed
the
fix/rfc2047-param-fold-infinite-loop
branch
from
July 28, 2026 08:15
23a4ea5 to
e7646bb
Compare
Contributor
Author
|
@alecpl Encoded words are now capped to 75 characters. |
alecpl
pushed a commit
that referenced
this pull request
Aug 2, 2026
…34) encodeHeaderValue() splits a value into chunks of 75 - strlen("=?<charset>?B?" . "?=") characters and never clamped that size. A charset name of 65 characters or more drove it to zero, so substr($value, 0, 0) returned '' and substr($value, 0) returned the value unchanged: the loop consumed nothing while appending an encoded-word prefix and suffix to the output on every pass, until PHP ran out of memory. This is the defect fixed for buildRFC2047Param() in 2001739 (#32), surviving in the header-value path that commit did not touch. Apply the same 48-character charset cap here, and extract it into Mail_mimePart::MAX_CHARSET_LENGTH so the two call sites cannot drift. The longest charset name mbstring recognises is 23 characters, so a valid name is never truncated. Additionally clamp the derived chunk lengths. A large $prefix_len could already drive the first-line budget negative independently of the charset, which fed an invalid ".{0,-n}" pattern to preg_match() in the quoted-printable branch. The buggy path is only reached when encodeMB() declines, i.e. without ext/mbstring, or with it on PHP < 8.0 where mb_strlen() returns false for an unknown encoding rather than throwing. Not addressed here: the quoted-printable splitter can still emit encoded-words of up to 77 characters, because "(.{0,$maxLength}[^\=][^\=])" matches up to two characters past its bound. That is independent of the charset length (it also happens with a one-character charset name) so it is left for a separate fix.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Hello,
I noticed a lot of these errors on our production Roundcube 1.7.2 instances...
... and eventually:
AI use disclosure: Anthropic Claude Opus 4.8 helped me review and improve my fix for the quoted-printable branch and extended it to cover the base64 branch as well. It also generated the detailed comment below.
buildRFC2047Param()could loop forever and exhaust memory when an encoded parameter (e.g. a long or non-ASCII attachment filename) can't be folded within the wrap width, i.e. when the parameter name makes$add_lenexceed$maxLengthand the available$lengthgoes negative.$real_lenbecomes <= 0, so$_quoteis an empty string and$valueis likewise never advanced.preg_match()fail, so$_quoteis never set ("Undefined variable$_quote" warning on PHP 8) andsubstr($value, strlen($_quote))never advances.In both cases the
while()condition stays true and$quotedgrows until the PHP memory_limit is hit (fatal error): a single crafted header is enough to DoS the process.Break out of both loops when nothing more can be folded; the trailing "
$quoted .= $prefix . $value . $suffix" then emits the remainder as a single (over-long but valid) encoded word.