python / python/cpython

mailbox.MH.get_sequences() unnecessarily materializes large sequence ranges

Ouverte
#156,379 1 commentaire 0 réactions 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

performance stdlib topic-email type-feature
Langage dominant
Python
Étoiles
77.2k
Forks
35.9k
Métriques de merge des PR
Métriques de PR en attente

Description

Feature or enhancement

Proposal:

Hello Python Security Response Team,

I am reporting a resource-exhaustion issue in CPython's mailbox.MH implementation. MH.get_sequences() materializes every integer in a range from the .mh_sequences metadata file before filtering the result against the message keys that actually exist. A normal MH.get_message() call invokes get_sequences(), so a tiny metadata file can cause a large allocation while loading an otherwise ordinary message.
Reproduction:

Please see the attached plain-text proof-of-concept, cpython-mh-sequence-repro.py. It creates a temporary MH mailbox containing one message and a small .mh_sequences file, then calls MH.get_message(1).

On macOS with Python 3.14.6:

$ python3 cpython-mh-sequence-repro.py --stop 5000000
python=3.14.6
stop=5000000 metadata_bytes=18
elapsed=0.259s maxrss=451297280 outcome=message_loaded

Negative control:

$ python3 cpython-mh-sequence-repro.py --stop 5
python=3.14.6
stop=5 metadata_bytes=12
elapsed=0.001s maxrss=23674880 outcome=message_loaded

The latest main ASAN build also reproduced the behavior with a one-million range: 18 bytes of metadata reached approximately 223 MB maximum RSS while loading the single message. No ASAN diagnostic occurred; this report is not claiming a memory-safety issue.

Root cause:

In Lib/mailbox.py, MH.get_sequences() effectively executes keys.update(range(start, stop + 1)) and only afterwards filters the result against all_keys. The final result only contains existing mailbox keys, so the declared interval does not need to be enumerated.

Suggested mitigation:

Intersect each declared range with all_keys instead of enumerating the interval, for example:

if start <= stop:
    keys.update(key for key in all_keys if start <= key <= stop)

The fix should preserve existing malformed-input behavior, ordering, and empty-sequence removal, and should add a regression test using a very large range with a small mailbox.

I searched the CPython issue and pull-request tracker for get_sequences, .mh_sequences, keys.update(range, and MH mailbox DoS. I did not find a matching report. Existing MH issues concerning Claws Mail sequence-file formats, missing .mh_sequences files, and replacement data loss are different behaviors.

POC is here.

#!/usr/bin/env python3
"""Measure MH sequence-range materialization from a tiny mailbox file."""

from __future__ import annotations

import argparse
from pathlib import Path
import mailbox
import resource
import sys
import tempfile
import time


def main() -> int:
    parser = argparse.ArgumentParser()
    parser.add_argument("--stop", type=int, default=1_000_000)
    args = parser.parse_args()
    with tempfile.TemporaryDirectory(prefix="cpython-mh-") as tmp:
        root = Path(tmp)
        (root / "1").write_bytes(b"Subject: canary\n\nbody\n")
        (root / ".mh_sequences").write_text(
            f"unseen: 1-{args.stop}\n", encoding="ascii"
        )
        box = mailbox.MH(root)
        start = time.monotonic()
        try:
            box.get_message(1)
            outcome = "message_loaded"
        except BaseException as exc:
            outcome = f"{type(exc).__name__}: {exc}"
        elapsed = time.monotonic() - start
        maxrss = resource.getrusage(resource.RUSAGE_SELF).ru_maxrss
        print(f"python={sys.version.split()[0]}")
        print(f"stop={args.stop} metadata_bytes={(root / '.mh_sequences').stat().st_size}")
        print(f"elapsed={elapsed:.3f}s maxrss={maxrss} outcome={outcome}")
        return 1 if args.stop >= 1_000_000 else 0


if __name__ == "__main__":
    raise SystemExit(main())
Has this already been discussed elsewhere?

No response given

Links to previous discussion of this feature:

No response

Linked PRs
  • gh-156442

Guide de contribution

Ouvrir le guide de contribution

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

L’implémentation concernée se trouve dans Lib/mailbox.py, plus précisément dans MH.get_sequences(); commencez par là et examinez les tests existants de mailbox. Ajoutez un test de régression utilisant une plage très grande avec une petite mailbox, tout en préservant le comportement pour les entrées malformées, l’ordre et la suppression des séquences vides.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
backend, security
Type d'issue
Bug
Difficulté
3/5
Temps estimé
1-2 jours
Activité
À l'abandon
Clarté
Clairement spécifiée
Accessibilité débutants
35/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.