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

Python Enhancement Proposals

PEP 330 – Python Bytecode Verifikation

Autor:
Michel Pelletier <michel at users.sourceforge.net>
Status:
Abgelehnt
Typ:
Standards Track
Erstellt:
17-Jun-2004
Python-Version:
2.6
Post-History:


Inhaltsverzeichnis

Zusammenfassung

Wenn der Bytecode der Python Virtual Machine (PVM) nicht „wohlgeformt“ ist, ist es möglich, die PVM durch verschiedene Fehler wie Unter- oder Überlauf des Wertestapels oder Lesen/Schreiben in beliebige Bereiche des PVM-Programmspeichers zum Absturz zu bringen oder auszunutzen. Die meisten dieser Fehlerarten können durch die Verifikation, dass der PVM-Bytecode eine Reihe einfacher Einschränkungen vor der Ausführung nicht verletzt, eliminiert werden.

Dieses PEP schlägt eine Reihe von Einschränkungen für das Format und die Struktur des Bytecodes der Python Virtual Machine (PVM) vor und stellt eine Implementierung dieses Verifikationsprozesses in Python bereit.

Bekanntmachung

Guido glaubt, dass ein Verifikationswerkzeug einen gewissen Wert hat. Wenn jemand es zu Tools/scripts hinzufügen möchte, ist kein PEP erforderlich.

Ein solches Werkzeug kann einen Wert für die Validierung der Ausgabe von „bytecodehacks“ oder von direkten Bearbeitungen von PYC-Dateien haben. Als Sicherheitsmaßnahme ist sein Wert etwas begrenzt, da perfekt gültiger Bytecode immer noch schreckliche Dinge tun kann. Diese Situation könnte sich ändern, wenn das Konzept der eingeschränkten Ausführung erfolgreich wiederbelebt würde.

Motivation

Die Python Virtual Machine führt Python-Programme aus, die aus der Python-Sprache in eine Bytecode-Repräsentation kompiliert wurden. Die PVM geht davon aus, dass jeder ausgeführte Bytecode im Hinblick auf eine Reihe impliziter Einschränkungen „wohlgeformt“ ist. Einige dieser Einschränkungen werden zur Laufzeit überprüft, die meisten jedoch nicht aufgrund des Overheads, den sie verursachen würden.

Im Debug-Modus führt die PVM mehrere Laufzeitprüfungen durch, um sicherzustellen, dass kein bestimmter Bytecode diese Einschränkungen verletzen kann, die den Bytecode bis zu einem gewissen Grad davor schützen, den Interpreter zum Absturz zu bringen oder auszunutzen. Diese Prüfungen führen zu einem messbaren Overhead für den Interpreter und werden im normalen Gebrauch typischerweise deaktiviert.

Bytecode, der nicht wohlgeformt ist und von einer PVM ausgeführt wird, die nicht im Debug-Modus läuft, kann eine Vielzahl von fatalen und nicht-fatalen Fehlern verursachen. Typischerweise wird ill-formeder Code dazu führen, dass die PVM einen Segfault auslöst und das Betriebssystem den Interpreter sofort und abrupt beendet.

Denkbar ist, dass ill-formeder Bytecode den Interpreter ausnutzen und Python-Bytecode die Ausführung beliebiger C-Level-Maschinenbefehle ermöglichen oder private, interne Datenstrukturen im Interpreter modifizieren könnte. Wenn dies geschickt eingesetzt wird, könnte dies jede Form von Sicherheitsrichtlinie untergraben, die eine Anwendung auf ihre Objekte anwenden möchte.

Praktisch wäre es schwierig für einen böswilligen Benutzer, „ungültigen“ Bytecode in eine PVM zum Zwecke der Ausbeutung einzuschleusen, aber nicht unmöglich. Pufferüberlauf- und Speicherüberschreibungsangriffe sind allgemein verstanden, insbesondere wenn die Exploit-Payload unverschlüsselt über ein Netzwerk übertragen wird oder wenn eine Schwäche bei Datei- oder Netzwerksicherheitsberechtigungen als Sprungbrett für weitere Angriffe genutzt wird.

Idealerweise sollte kein Bytecode jemals unterliegende C-Level-Datenstrukturen lesen oder schreiben dürfen, um die Funktionsweise der PVM zu untergraben, unabhängig davon, ob der Bytecode böswillig erstellt wurde oder nicht. Ein einfacher Verifikationsschritt vor der Ausführung könnte sicherstellen, dass Bytecode zur Laufzeit den Wertestapel nicht über- oder unterlaufen oder auf andere sensible Bereiche des PVM-Programmspeichers zugreifen kann.

Dieses PEP schlägt mehrere Validierungsschritte vor, die für Python-Bytecode vor dessen Ausführung durch die PVM durchgeführt werden sollten, damit er statische und strukturelle Einschränkungen für seine Instruktionen und deren Operanden einhält. Diese Schritte sind einfach und fangen eine große Klasse von ungültigem Bytecode ab, der Abstürze verursachen kann. Es besteht auch die Möglichkeit, dass einige Laufzeitprüfungen durch einen Verifikationsdurchlauf vorab eliminiert werden können.

Es gibt natürlich keine Möglichkeit zu verifizieren, dass Bytecode „vollständig sicher“ ist, für jede Definition von vollständig und sicher. Selbst mit Bytecode-Verifikation können Python-Programme aus einer Vielzahl von Gründen abstürzen und weiterhin viele verschiedene Klassen von Laufzeitfehlern verursachen, ob fatal oder nicht, und dies wird in Zukunft wahrscheinlich auch so bleiben. Der hier vorgeschlagene Verifikationsschritt schließt einfach eine leichte Lücke, die eine große Klasse von fatalen und subtilen Fehlern auf Bytecode-Ebene verursachen kann.

Derzeit verifiziert die Java Virtual Machine (JVM) Java-Bytecode auf eine Weise, die der hier vorgeschlagenen sehr ähnlich ist. Die JVM-Spezifikation Version 2 [1], Abschnitte 4.8 und 4.9 wurden daher als Grundlage für einige der unten erläuterten Einschränkungen verwendet. Jede Python-Bytecode-Verifikationsimplementierung muss diese Einschränkungen mindestens einhalten, ist aber nicht darauf beschränkt.

Statische Einschränkungen für Bytecode-Instruktionen

  1. Die Bytecode-Zeichenkette darf nicht leer sein. (len(co_code) > 0).
  2. Die Bytecode-Zeichenkette darf eine maximale Größe nicht überschreiten (len(co_code) < sizeof(unsigned char) - 1).
  3. Die erste Instruktion in der Bytecode-Zeichenkette beginnt am Index 0.
  4. Nur gültige Bytecodes mit der korrekten Anzahl von Operanden dürfen in der Bytecode-Zeichenkette enthalten sein.

Statische Einschränkungen für Operanden von Bytecode-Instruktionen

  1. Das Ziel einer Sprunganweisung muss innerhalb der Code-Grenzen liegen und auf eine Instruktion fallen, niemals zwischen eine Instruktion und ihre Operanden.
  2. Der Operand einer LOAD_*-Instruktion muss ein gültiger Index in seiner entsprechenden Datenstruktur sein.
  3. Der Operand einer STORE_*-Instruktion muss ein gültiger Index in seiner entsprechenden Datenstruktur sein.

Strukturelle Einschränkungen zwischen Bytecode-Instruktionen

  1. Jede Instruktion muss nur mit der geeigneten Anzahl von Argumenten im Wertestapel ausgeführt werden, unabhängig vom Ausführungspfad, der zu ihrer Auslösung führt.
  2. Wenn eine Instruktion entlang mehrerer verschiedener Ausführungspfade ausgeführt werden kann, muss der Wertestapel vor der Ausführung der Instruktion die gleiche Tiefe haben, unabhängig vom gewählten Pfad.
  3. Zu keinem Zeitpunkt während der Ausführung darf der Wertestapel eine Tiefe erreichen, die größer ist als die von co_stacksize implizierte.
  4. Die Ausführung fällt nie vom Ende von co_code.

Implementierung

Dieses PEP ist das Arbeitsdokument für eine Python-Bytecode-Verifikationsimplementierung, die in Python geschrieben ist. Diese Implementierung wird von der PVM nicht implizit vor der Ausführung von Bytecode verwendet, sondern soll explizit von Benutzern verwendet werden, die sich über möglicherweise ungültigen Bytecode Sorgen machen, mit dem folgenden Schnipsel

import verify
verify.verify(object)

Das Modul verify bietet eine Funktion verify, die die gleichen Argumente wie dis.dis akzeptiert: Klassen, Methoden, Funktionen oder Codeobjekte. Sie verifiziert, dass der Bytecode des Objekts gemäß den Spezifikationen dieses PEP wohlgeformt ist.

Wenn der Code wohlgeformt ist, gibt der Aufruf von verify ohne Fehler zurück. Wenn ein Fehler auftritt, löst er eine VerificationError aus, deren Argument den Grund für das Scheitern angibt. Es liegt am Programmierer, ob er den Fehler auf irgendeine Weise behandelt oder den ungültigen Code trotzdem ausführt.

Phillip Eby hat einen Pseudo-Code-Algorithmus für die Bytecode-Stapeltiefenverifikation vorgeschlagen, der von der Referenzimplementierung verwendet wird.

Verifikationsprobleme

Dieses PEP beschreibt nur eine kleine Anzahl von Verifikationen. Während Diskussionen und Analysen zu vielen weiteren führen werden, ist es sehr wahrscheinlich, dass zukünftige Verifikationen oder benutzerdefinierte, projektspezifische Verifikationen erforderlich sein werden. Aus diesem Grund könnte es wünschenswert sein, der Testimplementierung eine Verifikationsregistrierungsschnittstelle hinzuzufügen, um zukünftige Verifikationen zu registrieren. Die Notwendigkeit hierfür ist minimal, da benutzerdefinierte Verifikationen die aktuelle Implementierung als Basisklasse verwenden und erweitern können, um zusätzliches Verhalten hinzuzufügen.

Erforderliche Änderungen

Armin Rigo bemerkte, dass mehrere Bytecodes geändert werden müssen, damit ihre Stapelwirkung statisch analysiert werden kann. Dies sind END_FINALLY, POP_BLOCK und MAKE_CLOSURE. Armin und Guido haben sich bereits darauf geeinigt, wie die Instruktionen korrigiert werden. Derzeit behandelt die Python-Implementierung diese Instruktionen noch nicht.

Dieses PEP schlägt nicht vor, den Verifikationsschritt zum Interpreter hinzuzufügen, sondern nur die Python-Implementierung in der Standardbibliothek zur optionalen Verwendung bereitzustellen. Ob diese Verifikation in C übersetzt, mit der PVM gebündelt oder in irgendeiner Weise erzwungen wird, bleibt zukünftigen Diskussionen vorbehalten.

Referenzen


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

Last modified: 2025-02-01 08:59:27 GMT