Following system colour scheme Selected dark colour scheme Selected light colour scheme

Python Enhancement Proposals

PEP 552 – Deterministische pycs

Autor:
Benjamin Peterson <benjamin at python.org>
Status:
Final
Typ:
Standards Track
Erstellt:
04-Sep-2017
Python-Version:
3.7
Post-History:
07-Sep-2017
Resolution:
Python-Dev Nachricht

Inhaltsverzeichnis

Zusammenfassung

Dieser PEP schlägt eine Erweiterung des pyc-Formats vor, um es deterministischer zu machen.

Begründung

Ein reproduzierbarer Build ist einer, bei dem bei jedem Build desselben Quellcodes byte-für-byte derselbe Output erzeugt wird – sogar auf verschiedenen Maschinen (natürlich unter der Voraussetzung, dass sie über relativ ähnliche Umgebungen verfügen). Reproduzierbarkeit ist wichtig für die Sicherheit. Sie ist auch ein Schlüsselkonzept in inhaltsbasierten Build-Systemen wie Bazel, die am effektivsten sind, wenn der Inhalt von Ausgabedateien eine deterministische Funktion des Inhalts von Eingabedateien ist.

Das aktuelle Python pyc-Format ist das marshalled Code-Objekt des Moduls, vorangestellt von einer Magischen Zahl, dem Zeitstempel der Quelle und der Dateigröße der Quelle. Die Anwesenheit eines Zeitstempels der Quelle bedeutet, dass eine pyc-Datei keine deterministische Funktion des Inhalts der Eingabedatei ist – sie hängt auch von flüchtigen Metadaten ab, nämlich der mtime der Quelle. Somit sind pyc-Dateien ein Hindernis für die ordnungsgemäße Reproduzierbarkeit.

Distributoren von Python-Code sind derzeit auf die folgenden Optionen beschränkt:

  1. keine pyc-Dateien verteilen und die Caching-Vorteile verlieren
  2. pyc-Dateien verteilen und die Reproduzierbarkeit verlieren
  3. allen Python-Quellcodedateien sorgfältig einen deterministischen Zeitstempel geben (siehe zum Beispiel https://github.com/python/cpython/pull/296)
  4. eine komplizierte Mischung aus 1. und 2. durchführen, z.B. pyc-Dateien zur Installationszeit generieren

Keine dieser Optionen ist sehr attraktiv. Dieser PEP schlägt vor, den Zeitstempel durch einen deterministischen Hash zu ersetzen. Die aktuelle Zeitstempel-Invalidierungsmethode bleibt jedoch standardmäßig bestehen. Trotz ihrer Nicht-Determinismus funktioniert die Zeitstempel-Invalidierung für viele Workflows und Anwendungsfälle gut. Das hash-basierte pyc-Format kann die Kosten für das Lesen und Hashing jeder Quelldatei mit sich bringen, was teurer ist als das einfache Überprüfen von Zeitstempeln. Daher erwarten wir, dass es vorerst hauptsächlich von Distributoren und für Power-User-Anwendungsfälle verwendet wird.

(Beachten Sie, dass es noch andere Probleme gibt [1] [2], die wir hier nicht behandeln, die pyc-Dateien nicht-deterministisch machen können.)

Spezifikation

Der pyc-Header besteht derzeit aus 3 32-Bit-Wörtern. Wir werden ihn auf 4 erweitern. Das erste Wort bleibt die Magische Zahl, die die Bytecode- und pyc-Format-Version angibt. Das zweite Wort, konzeptionell das neue Wort, wird ein Bitfeld sein. Die Interpretation des restlichen Headers und das Invalidierungsverhalten der pyc-Datei hängen vom Inhalt des Bitfeldes ab.

Wenn das Bitfeld 0 ist, handelt es sich bei der pyc-Datei um eine traditionelle zeitstempelbasierte pyc-Datei. Das heißt, das dritte und vierte Wort sind der Zeitstempel und die Dateigröße. Die Invalidierung erfolgt durch Vergleichen der Metadaten der Quelldatei mit denen im Header.

Wenn das niedrigste Bit des Bitfeldes gesetzt ist, handelt es sich bei der pyc-Datei um eine hash-basierte pyc-Datei. Das zweitniedrigste Bit nennen wir das check_source Flag. Nach dem Bitfeld folgt ein 64-Bit-Hash der Quelldatei. Wir werden einen SipHash mit einem hartkodierten Schlüssel des Inhalts der Quelldatei verwenden. Ein anderer schneller Hash wie MD5 oder BLAKE2 würde ebenfalls funktionieren. Wir wählen SipHash, da Python bereits eine integrierte Implementierung davon aus PEP 456 hat, obwohl eine Schnittstelle, die die Auswahl des SipHash-Schlüssels ermöglicht, für Python verfügbar gemacht werden muss. Die Sicherheit des Hashs ist keine Sorge, obwohl wir völlig unsichere Hashes wie MD5 überspringen, um die Überprüfung von Python in kontrollierten Umgebungen zu erleichtern.

Wenn Python auf eine hash-basierte pyc-Datei stößt, hängt sein Verhalten von der Einstellung des check_source Flags ab. Wenn das check_source Flag gesetzt ist, wird Python die Gültigkeit der pyc-Datei durch Hashing der Quelldatei und Vergleichen des Hashes mit dem erwarteten Hash in der pyc-Datei bestimmen. Wenn die pyc-Datei neu generiert werden muss, wird sie erneut als hash-basierte pyc-Datei mit gesetztem check_source Flag neu generiert.

Für hash-basierte pyc-Dateien, bei denen check_source nicht gesetzt ist, lädt Python die pyc-Datei einfach, ohne den Hash der Quelldatei zu überprüfen. Die Erwartung ist in diesem Fall, dass ein externes System (z.B. der lokale Linux-Distribution-Paketmanager) für die Aktualisierung der pyc-Dateien verantwortlich ist, sodass Python selbst nicht prüfen muss. Auch wenn die Validierung deaktiviert ist, sollte das Hash-Feld korrekt gesetzt sein, damit externe Konsistenzprüfer die Aktualität der pyc-Datei verifizieren können. Beachten Sie auch, dass die PEP 3147 Regel, dass pyc-Dateien ohne entsprechende Quelldateien nicht geladen werden dürfen, auch für hash-basierte pyc-Dateien weiterhin durchgesetzt wird.

Die programmatischen APIs von py_compile und compileall werden die Generierung von hash-basierten pyc-Dateien unterstützen. Hauptsächlich wird py_compile eine neue Enumeration definieren, die allen verfügbaren pyc-Invalidierungsmodulen entspricht.

class PycInvalidationMode(Enum):
    TIMESTAMP
    CHECKED_HASH
    UNCHECKED_HASH

py_compile.compile, compileall.compile_dir und compileall.compile_file erhalten alle einen Parameter invalidation_mode, der einen Wert der Enumeration PycInvalidationMode akzeptiert.

Das Werkzeug compileall wird um eine neue Kommandozeilenoption --invalidation-mode erweitert, um hash-basierte pyc-Dateien mit und ohne gesetztes check_source Bit zu generieren. --invalidation-mode ist eine dreiwertige Option, die die Werte timestamp (der Standard), checked-hash und unchecked-hash annimmt, die den Werten von PycInvalidationMode entsprechen.

importlib.util wird um eine Funktion source_hash(source) erweitert, die den von der pyc-Schreibroutine für einen Byte-String **source** verwendeten Hash berechnet.

Die Laufzeitkonfiguration der hash-basierten pyc-Invalidierung wird durch eine neue Interpreter-Option --check-hash-based-pycs erleichtert. Dies ist eine dreiwertige Option, die 3 Werte annehmen kann: default, always und never. Der Standardwert default bedeutet, dass das check_source Flag in hash-basierten pyc-Dateien die Invalidierung bestimmt, wie oben beschrieben. always veranlasst den Interpreter, die Quelldatei unabhängig vom Wert des check_source Bits für die Invalidierung zu hashen. never veranlasst den Interpreter, hash-basierte pyc-Dateien immer als gültig anzunehmen. Wenn --check-hash-based-pycs=never wirksam ist, werden ungeprüfte hash-basierte pyc-Dateien als ungeprüfte hash-basierte pyc-Dateien neu generiert. Zeitstempelbasierte pyc-Dateien werden von --check-hash-based-pycs nicht beeinflusst.

Referenzen

Danksagungen

Der Autor möchte Gregory P. Smith, Christian Heimes und Steve Dower für nützliche Gespräche über das Thema dieses PEPs danken.


Source: https://github.com/python/peps/blob/main/peps/pep-0552.rst

Zuletzt geändert: 2025-02-01 08:59:27 GMT