Skip to content

gh-138223: Fix Infinite loop in email._header_value_parser._fold_mime_parameters when parameter names are too long#138231

Open
ChenyangLi4288 wants to merge 13 commits intopython:mainfrom
ChenyangLi4288:main
Open

gh-138223: Fix Infinite loop in email._header_value_parser._fold_mime_parameters when parameter names are too long#138231
ChenyangLi4288 wants to merge 13 commits intopython:mainfrom
ChenyangLi4288:main

Conversation

@ChenyangLi4288
Copy link
Copy Markdown

@ChenyangLi4288 ChenyangLi4288 commented Aug 28, 2025

The infinite loop occurred in _fold_mime_parameters() when processing MIME parameters
with very long keys (64 characters) during RFC 2231 encoding.

Changes made:

  1. In email._header_value_parser._fold_mime_parameters():

    • Replace infinite 'while True:' loop with 'while splitpoint > 1:'
    • Ensure splitpoint is always at least 1 to prevent getting stuck
    • Add fallback logic to force minimal splits when values cannot fit
  2. In email.header._append_chunk():

    • Add comment explaining handling of extremely long strings that can't be split

This fixes GitHub issue #138223 where add_attachment() with long parameter
keys would cause as_string() to hang indefinitely during MIME parameter
folding and header processing.

… parameter keys

The infinite loop occurred in _fold_mime_parameters() when processing MIME parameters
with very long keys (64 characters) during RFC 2231 encoding. The issue was in
two locations:

1. In email._header_value_parser._fold_mime_parameters():
   - Replace infinite 'while True:' loop with 'while splitpoint > 1:'
   - Ensure splitpoint is always at least 1 to prevent getting stuck
   - Add fallback logic to force minimal splits when values cannot fit

2. In email.header._ValueFormatter._append_chunk():
   - Add safety check for extremely long strings that cannot be split
   - Force line breaks when no suitable split points are found
   - Prevent infinite loops in header folding for edge cases

This fixes GitHub issue python#138223 where add_attachment() with long parameter
keys would cause as_string() to hang indefinitely during MIME parameter
folding and header processing.
… parameter keys

The infinite loop occurred in _fold_mime_parameters() when processing MIME parameters
with very long keys (64 characters) during RFC 2231 encoding.

Changes made:
1. In email._header_value_parser._fold_mime_parameters():
   - Replace infinite 'while True:' loop with 'while splitpoint > 1:'
   - Ensure splitpoint is always at least 1 to prevent getting stuck
   - Add fallback logic to force minimal splits when values cannot fit

2. In email.header._append_chunk():
   - Add comment explaining handling of extremely long strings that can't be split

This fixes GitHub issue python#138223 where add_attachment() with long parameter
keys would cause as_string() to hang indefinitely during MIME parameter
folding and header processing.
Copy link
Copy Markdown
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does the RFC say about folding? Please add a regression test as well and move the blurb entry from library to security.

Comment thread Lib/email/_header_value_parser.py Outdated
Comment thread Lib/email/header.py Outdated
@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented Aug 28, 2025

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

…pythonGH-138223)

- Fix a variable scope issue with encoded_value in _fold_mime_parameters
- Add test case to reproduce and verify the fix
- Update NEWS entry for the security fix
@ChenyangLi4288
Copy link
Copy Markdown
Author

RFC 2231 specifying that:
Parameter values can be encoded and folded when they're too long
The folding must maintain the parameter's integrity while following RFC 5322's folding rules
When parameters are encoded (like with RFC 2231 encoding), they must still follow proper folding boundaries
In the context of this fix for issue #138223, the infinite loop was occurring in the _fold_mime_parameters function when it tried to fold excessively long parameter names. The RFCs don't specify a maximum length for parameter names, but they do require that folding operations complete in finite time and don't get stuck in infinite loops.

@ChenyangLi4288 ChenyangLi4288 requested a review from picnixz August 28, 2025 20:54
@picnixz
Copy link
Copy Markdown
Member

picnixz commented Aug 28, 2025

The RFCs don't specify a maximum length for parameter names

They do, it's 126 reserved characters (it's in the EBNF)

they do require that folding operations complete in finite time and don't get stuck in infinite loops.

In this case, I think forcing the writing is fine.


OTOH, does the RFC allow splitting the parameter names in a good way? (and how)

@ChenyangLi4288
Copy link
Copy Markdown
Author

  1. FC Parameter Name Length Limit
    Thank you for reminding! I checked EBNF and confirmed length of 126.
  2. Parameter Name Splitting
    RFC 2231 does NOT allow splitting parameter names. The RFC specifically states that parameter names themselves cannot be split or folded using the continuation mechanism. Only parameter values can be split.
  3. The Fix Approach
    Given that:
    1)Parameter names cannot be split (RFC restriction)
    2)Parameter names have a 126-character limit (RFC specification)
    3)Folding must complete in finite time (RFC requirement)
    I think forcing the writing maintains RFC compliance while preventing the infinite loop.

@picnixz
Copy link
Copy Markdown
Member

picnixz commented Aug 28, 2025

RFC 2231 does NOT allow splitting parameter names. The RFC specifically states that parameter names themselves cannot be split or folded using the continuation mechanism

Could you pinpoint the location of the RFC where it's stated, so that we also add it at the source code level as a comment? TiA.

Copy link
Copy Markdown
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The tests are incomplete and incorrect as the length limit is dynamic and depends on the length of the parameter's values, and please:

  • don't create a new file,
  • don't catch unexpected exceptions,
  • verify that main hangs with the test inputs. The bad values depend on the values.

Comment thread Lib/test/test_email/test_infinite_loop_fix.py Outdated
Comment thread Lib/test/test_email/test_infinite_loop_fix.py Outdated
Comment thread Lib/test/test_email/test_infinite_loop_fix.py Outdated
Comment thread Lib/test/test_email/test_infinite_loop_fix.py Outdated
Comment thread Lib/test/test_email/test_infinite_loop_fix.py Outdated
@picnixz picnixz marked this pull request as draft August 28, 2025 21:24
@picnixz
Copy link
Copy Markdown
Member

picnixz commented Aug 28, 2025

I'm converting it into a draft until comments are addressed.

…d_mime_parameters

Fix infinite loop that occurred when processing MIME parameters with very long
parameter names during RFC 2231 encoding. The issue was in the while loop
that tried to find split points for long parameter values.

Changes made:
- In email._header_value_parser._fold_mime_parameters(): Replace infinite
  'while True:' loop with 'while splitpoint > 1:' and ensure splitpoint is
  always at least 1 to prevent getting stuck
- Add fallback logic to force minimal splits when values cannot fit

Testing:
- Added test_mime_parameter_folding_no_infinite_loop to TestMIMEPart class
- Test creates scenario where maxchars = 1 (edge case that caused infinite loop)
- Verifies as_string() completes successfully instead of hanging
- Test passes, confirming the fix prevents infinite loops

This fixes GitHub issue python#138223 where add_attachment() with long parameter
keys would cause as_string() to hang indefinitely during MIME parameter
folding and header processing.
@ChenyangLi4288
Copy link
Copy Markdown
Author

It doesn't EXPLICITLY state that parameter names cannot be split.
What RFC 2231 does specify is:
Purpose and scope (Section 3): "The asterisk character ("*") followed by a decimal count is employed to indicate that multiple parameters are being used to encapsulate a single parameter value" - this describes the mechanism as being for splitting values, not names.

All examples in section 7 show splitting values across multiple complete parameter names, never splitting the parameter name itself. So the prohibition against splitting parameter names is inferred from the RFC's design and purpose rather than explicitly stated.

@ChenyangLi4288 ChenyangLi4288 marked this pull request as ready for review August 29, 2025 18:44
@ChenyangLi4288 ChenyangLi4288 requested a review from picnixz August 29, 2025 18:44
@picnixz
Copy link
Copy Markdown
Member

picnixz commented Aug 29, 2025

It doesn't EXPLICITLY state that parameter names cannot be split.

As there is no mechanism to split them and since the EBNF doesn't allow partial names, I think we should consider this to be the norm. @bitdancer any recommendation here?

@picnixz
Copy link
Copy Markdown
Member

picnixz commented Aug 29, 2025

Please, stop merging main into your branch unless there is a conflict to solve. It wastes resources.

@ChenyangLi4288
Copy link
Copy Markdown
Author

Sorry for that, will stop doing that.

@ChenyangLi4288
Copy link
Copy Markdown
Author

any updates on that?

Comment thread Lib/email/_header_value_parser.py Outdated
Comment thread Lib/test/test_email/test_message.py Outdated
Comment thread Lib/test/test_email/test_message.py Outdated
Comment thread Lib/test/test_email/test_message.py Outdated
@bedevere-app
Copy link
Copy Markdown

bedevere-app Bot commented Sep 3, 2025

A Python core developer has requested some changes be made to your pull request before we can consider merging it. If you could please address their requests along with any other requests in other reviews from core developers that would be appreciated.

Once you have made the requested changes, please leave a comment on this pull request containing the phrase I have made the requested changes; please review again. I will then notify any core developers who have left a review that you're ready for them to take another look at this pull request.

@picnixz
Copy link
Copy Markdown
Member

picnixz commented Sep 3, 2025

any updates on that?

Usually, reviews can be requested after a month or so if the reviewers didn't reply since then. We don't always have the necessary bandwidth.

Copy link
Copy Markdown
Member

@picnixz picnixz left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for not addressing this review! I hope it will help but I think we need an email's maintainer for that (since we force a split...).

Comment thread Misc/NEWS.d/next/Security/2025-08-29-14-04-17.gh-issue-138223.gX_GJv.rst Outdated
Comment on lines +3083 to +3088
maxchars = maxlen - chrome_len - 2
# Ensure maxchars is at least 1 to prevent negative values
if maxchars <= 0:
maxchars = 1
splitpoint = maxchars
splitpoint = max(1, splitpoint) # Ensure splitpoint is always at least 1
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
maxchars = maxlen - chrome_len - 2
# Ensure maxchars is at least 1 to prevent negative values
if maxchars <= 0:
maxchars = 1
splitpoint = maxchars
splitpoint = max(1, splitpoint) # Ensure splitpoint is always at least 1
maxchars = maxlen - chrome_len - 2
splitpoint = maxchars = max(1, maxchars)

Comment on lines 3080 to +3083
# have that, we'd be stuck, so in that case fall back to
# the RFC standard width.
maxlen = 78
splitpoint = maxchars = maxlen - chrome_len - 2
while True:
maxchars = maxlen - chrome_len - 2
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct me if I've misunderstood something, but I believe the simpler fix here is the following:

-               # have that, we'd be stuck, so in that case fall back to
-               # the RFC standard width.
-               maxlen = 78
+               # the RFC standard width, or the width of the chrome plus
+               # an arbitrary 10 if the parameter name is long.
+               maxlen = max(78, chrome_len + 10

With this change applied instead of yours, your test passes, at least. (I haven't reviewed the test yet; thought I'd post this first). My logic is that if we are already violating the user's request for maxwidth (and we already have a case where we're doing that), we might as well just make the new value something semi-reasonable, rather than "just enough" (one character more than the chrome).

If this isn't enough to protect against an infinite loop (and I haven't tried to reason through that yet, either), then we are going to need a better test.

Co-authored-by: Bénédikt Tran <10796600+picnixz@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants