python / python/cpython

`mailbox.MH.__setitem__()` can destroy a message when replacement fails

Aperta
#156,312 0 commenti 0 reazioni 0 assegnatari Vedi su GitHub

Nessuno ha ancora preso questa issue.

stdlib topic-email type-bug
Lingua principale
Python
Stelle
77.2k
Fork
35.9k
Metriche di merge delle PR
Metriche PR in attesa

Descrizione

Bug description:
Summary

`When mailbox.MH replaces an existing message with an invalid str, it correctly raises ValueError, but the original message file may already have been truncated to empty. In other words, the replacement fails while also destroying the existing message content, resulting in data loss.

Affected public API: mailbox.MH.__setitem__().

Minimal reproducer

Run this against CPython before the fix:

import mailbox
import tempfile

with tempfile.TemporaryDirectory() as path:
    box = mailbox.MH(path)
    key = box.add(b"Subject: original\n\noriginal body\n")
    original = box.get_bytes(key)

    try:
        box[key] = "Subject: caf\u00e9\n\nreplacement body\n"
    except ValueError as exc:
        print(type(exc).__name__, exc)

    print("in-memory:", box.get_bytes(key))
    box.close()

    reopened = mailbox.MH(path)
    print("reopened:", reopened.get_bytes(key))
    assert reopened.get_bytes(key) == original

Actual result before the fix:

ValueError String input must be ASCII-only; use bytes or a Message instead
in-memory: b''
reopened: b''
AssertionError

Expected result:

ValueError String input must be ASCII-only; use bytes or a Message instead
in-memory: b'Subject: original\n\noriginal body\n'
reopened: b'Subject: original\n\noriginal body\n'

The same problem occurs if a file-like message object raises while it is being read: the old message is replaced with the bytes written before the exception.

Root cause

MH.__setitem__() performed these operations in this order:

open existing message
-> open the same path with O_TRUNC
-> serialize the replacement with _dump_message()
-> propagate a serialization error

O_TRUNC changes the existing message file before _dump_message() validates or fully reads the replacement. For a non-ASCII str, _dump_message() calls _string_to_bytes() and raises ValueError immediately, leaving the already truncated file in place. There is no rollback path.

CPython versions tested on:

CPython main branch

Operating systems tested on:

Linux

Linked PRs
  • gh-156313

Guida per i contributori

Apri la guida per i contributori

Come iniziare

  1. Leggi tutta la issue e poi la guida ai contributi del progetto.
  2. Commenta sulla issue per dire che te ne occupi tu — evita che due persone facciano lo stesso lavoro.
  3. Fai un fork del repository e lavora su un branch.
  4. Apri una pull request che faccia riferimento al numero della issue.

Direzione di ricerca

Inizia con mailbox.MH.setitem() e _dump_message(), quindi esegui il riproduttore minimo dell’issue nei test di mailbox di CPython. Il lavoro è completato quando la sostituzione fallita di una stringa non ASCII o di un messaggio simile a un file che solleva un’eccezione lascia invariati sia il messaggio originale in memoria sia quello riaperto.

Scritto dal modello di indicizzazione a partire dal testo della issue.

Valutazione

Stack tecnologico
python
Ambito
backend
Tipo di issue
Bug
Difficoltà
3/5
Tempo stimato
1-2 giorni
Stato di attività
Ferma
Chiarezza
Specificata chiaramente
Idoneità per principianti
35/100

Ricevi le nuove issue nella tua casella

Un breve riepilogo di issue GitHub adatte ai principianti.