python / python/cpython

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

オープン
#156,379 コメント 1 件 リアクション 0 件 担当者 0 名 GitHub で見る

まだ誰も着手していません。

performance stdlib topic-email type-feature
主要言語
Python
スター
77.2k
フォーク
35.9k
PR マージ指標
PR 指標を取得中

説明

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

コントリビューションガイド

コントリビューションガイドを開く

はじめの一歩

  1. issue を最後まで読み、次にプロジェクトのコントリビューションガイドを読みます。
  2. 着手することを issue にコメントします — 二人が同じ作業をするのを防げます。
  3. リポジトリをフォークし、ブランチを切って変更します。
  4. issue 番号を参照したプルリクエストを送ります。

調査の方向性

影響を受ける実装は Lib/mailbox.py、具体的には MH.get_sequences() にあります。そこから始めて、既存の mailbox テストを調べてください。非常に大きな範囲と小さな mailbox を使用する回帰テストを追加し、不正な入力に対する動作、順序、および空のシーケンスの削除を維持してください。

索引モデルが issue の本文から書いたものです。

評価

技術スタック
python
領域
backend, security
issue の種類
バグ
難易度
3/5
見積もり時間
1〜2日
活発さ
停滞
明瞭さ
明確に書かれている
初心者へのやさしさ
35/100

新しい issue をメールで受け取る

初心者向けの GitHub issue を短くまとめたダイジェスト。