-
-
Notifications
You must be signed in to change notification settings - Fork 31.9k
gh-102988: Detect email address parsing errors and return empty tuple to indicate the parsing error (old API) #105127
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
9 commits
Select commit
Hold shift + click to select a range
81098ed
gh-102988: Detect email address parsing errors and return empty tuple…
tdwyer ecbb31c
r-prefix strs with \ in the tests
gpshead d8d2831
reword getaddresses docstring
gpshead b44a0b7
Update comment for getaddresses() and fix broken email test
tdwyer 40002d9
Add NEWS
tdwyer 86ffb57
Merge branch 'main' into fix_issues102988
gpshead 302ee76
Fix NEWS entry. It was not a Module its Class email._parseaddr.Addres…
tdwyer 48d9f58
Add email.utils docs, narrow the docstring.
gpshead f96a3ff
Add versionchanged to parseaddr, add What's New.
gpshead File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -65,6 +65,11 @@ of the new API. | |
*email address* parts. Returns a tuple of that information, unless the parse | ||
fails, in which case a 2-tuple of ``('', '')`` is returned. | ||
|
||
.. versionchanged:: 3.12 | ||
For security reasons, addresses that were ambiguous and could parse into | ||
multiple different addresses now cause ``('', '')`` to be returned | ||
instead of only one of the *potential* addresses. | ||
|
||
|
||
.. function:: formataddr(pair, charset='utf-8') | ||
|
||
|
@@ -87,7 +92,7 @@ of the new API. | |
This method returns a list of 2-tuples of the form returned by ``parseaddr()``. | ||
*fieldvalues* is a sequence of header field values as might be returned by | ||
:meth:`Message.get_all <email.message.Message.get_all>`. Here's a simple | ||
example that gets all the recipients of a message:: | ||
example that gets all the recipients of a message: | ||
|
||
from email.utils import getaddresses | ||
|
||
|
@@ -97,6 +102,25 @@ of the new API. | |
resent_ccs = msg.get_all('resent-cc', []) | ||
all_recipients = getaddresses(tos + ccs + resent_tos + resent_ccs) | ||
|
||
When parsing fails for a single fieldvalue, a 2-tuple of ``('', '')`` | ||
is returned in its place. Other errors in parsing the list of | ||
addresses such as a fieldvalue seemingly parsing into multiple | ||
addresses may result in a list containing a single empty 2-tuple | ||
``[('', '')]`` being returned rather than returning potentially | ||
invalid output. | ||
|
||
Example malformed input parsing: | ||
|
||
.. doctest:: | ||
|
||
>>> from email.utils import getaddresses | ||
>>> getaddresses(['[email protected] <[email protected]>', '[email protected]']) | ||
[('', '')] | ||
|
||
.. versionchanged:: 3.12 | ||
The 2-tuple of ``('', '')`` in the returned values when parsing | ||
fails were added as to address a security issue. | ||
|
||
|
||
.. function:: parsedate(date) | ||
|
||
|
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
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
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
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3319,15 +3319,90 @@ def test_getaddresses(self): | |
[('Al Person', '[email protected]'), | ||
('Bud Person', '[email protected]')]) | ||
|
||
def test_getaddresses_parsing_errors(self): | ||
"""Test for parsing errors from CVE-2023-27043""" | ||
eq = self.assertEqual | ||
eq(utils.getaddresses(['[email protected](<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected])<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected]<<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected]><[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected]@<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected],<[email protected]>']), | ||
[('', '[email protected]'), ('', '[email protected]')]) | ||
eq(utils.getaddresses(['[email protected];<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected]:<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected].<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected]"<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected][<[email protected]>']), | ||
[('', '')]) | ||
eq(utils.getaddresses(['[email protected]]<[email protected]>']), | ||
[('', '')]) | ||
|
||
def test_parseaddr_parsing_errors(self): | ||
"""Test for parsing errors from CVE-2023-27043""" | ||
eq = self.assertEqual | ||
eq(utils.parseaddr(['[email protected](<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected])<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected]<<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected]><[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected]@<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected],<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected];<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected]:<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected].<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected]"<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected][<[email protected]>']), | ||
('', '')) | ||
eq(utils.parseaddr(['[email protected]]<[email protected]>']), | ||
('', '')) | ||
|
||
def test_getaddresses_nasty(self): | ||
eq = self.assertEqual | ||
eq(utils.getaddresses(['foo: ;']), [('', '')]) | ||
eq(utils.getaddresses( | ||
['[]*-- =~$']), | ||
[('', ''), ('', ''), ('', '*--')]) | ||
eq(utils.getaddresses(['[]*-- =~$']), [('', '')]) | ||
eq(utils.getaddresses( | ||
['foo: ;', '"Jason R. Mastaler" <[email protected]>']), | ||
[('', ''), ('Jason R. Mastaler', '[email protected]')]) | ||
eq(utils.getaddresses( | ||
[r'Pete(A nice \) chap) <pete(his account)@silly.test(his host)>']), | ||
[('Pete (A nice ) chap his account his host)', '[email protected]')]) | ||
eq(utils.getaddresses( | ||
['(Empty list)(start)Undisclosed recipients :(nobody(I know))']), | ||
[('', '')]) | ||
eq(utils.getaddresses( | ||
['Mary <@machine.tld:[email protected]>, , jdoe@test . example']), | ||
[('Mary', '[email protected]'), ('', ''), ('', '[email protected]')]) | ||
eq(utils.getaddresses( | ||
['John Doe <jdoe@machine(comment). example>']), | ||
[('John Doe (comment)', '[email protected]')]) | ||
eq(utils.getaddresses( | ||
['"Mary Smith: Personal Account" <[email protected]>']), | ||
[('Mary Smith: Personal Account', '[email protected]')]) | ||
eq(utils.getaddresses( | ||
['Undisclosed recipients:;']), | ||
[('', '')]) | ||
eq(utils.getaddresses( | ||
[r'<[email protected]>, "Giant; \"Big\" Box" <[email protected]>']), | ||
[('', '[email protected]'), ('Giant; "Big" Box', '[email protected]')]) | ||
|
||
def test_getaddresses_embedded_comment(self): | ||
"""Test proper handling of a nested comment""" | ||
|
4 changes: 4 additions & 0 deletions
4
Misc/NEWS.d/next/Security/2023-06-13-20-52-24.gh-issue-102988.Kei7Vf.rst
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
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
CVE-2023-27043: Prevent :func:`email.utils.parseaddr` | ||
and :func:`email.utils.getaddresses` from returning the realname portion of an | ||
invalid RFC2822 email header in the email address portion of the 2-tuple | ||
returned after being parsed by :class:`email._parseaddr.AddressList`. |
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.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@tdwyer Is this right? Isn’t the next paragraph still a literal block? Why have you removed this colon?