Statement on glibc/iconv Vulnerability

Plugin-Architektur des MySQL Native Drivers

Dieser Abschnitt bietet einen Überblick über die Plugin-Architektur von mysqlnd.

Überblick über den MySQL Native Driver

Vor der Entwicklung eines mysqlnd-Plugins ist es nützlich, ein wenig über den Aufbau von mysqlnd selbst zu wissen. Mysqlnd besteht aus den folgenden Modulen:

Das Organisationsdiagramm von mysqlnd, pro Modul
Modul-Statistiken mysqlnd_statistics.c
Datenbankverbindung mysqlnd.c
Ergebnismenge mysqlnd_result.c
Metadaten der Ergebnismenge mysqlnd_result_meta.c
Anweisung mysqlnd_ps.c
Netzwerk mysqlnd_net.c
Wire-Protokoll mysqlnd_wireprotocol.c

Objektorientiertes Paradigma in C

Auf der Code-Ebene verwendet mysqlnd ein C-Pattern, um die Objektorientierung zu implementieren.

In C wird ein Objekt mittels struct beschrieben. Die Mitglieder dieser Struktur sind die Eigenschaften des Objekts. Die Strukturmitglieder, die auf Funktionen verweisen, stellen die Methoden dar.

Im Gegensatz zu anderen Sprachen wie C++ oder Java gibt es im objektorientierten Paradigma von C keine festen Regeln für die Vererbung. Es gibt jedoch einige Konventionen, die befolgt werden müssen und auf die später eingegangen wird.

Der PHP-Lebenszyklus

Der Lebenszyklus von PHP besteht aus 2 grundlegenden Zyklen:

  • Der Start- und Shutdown-Zyklus der PHP-Engine

  • Der Zyklus einer Anfrage

Wenn die PHP-Engine startet, ruft sie für jede registrierte Erweiterung die Funktion für die Modulinitialisierung (MINIT) auf. Dies ermöglicht es jedem Modul, Variablen zu definieren und Ressourcen zuzuweisen, die während der gesamten Lebensdauer des Prozesses der PHP-Engine vorhanden sind. Wenn die PHP-Engine herunterfährt, ruft sie für jede Erweiterung die Funktion für das Herunterfahren des Moduls (MSHUTDOWN) auf.

Während der Lebensdauer der PHP-Engine erhält sie eine Reihe von Anfragen. Jede Anfrage löst einen neuen Lebenszyklus aus. Bei jeder Anfrage ruft die PHP-Engine die Anfrage-Initialisierungsfunktion der jeweiligen Erweiterung auf. Die Erweiterung kann alle für die Bearbeitung der Anfrage erforderlichen Variablen definieren und Ressourcen zuweisen. Am Ende des Anfragezyklus ruft die Engine für jede Erweiterung die Funktion zum Herunterfahren der Anfrage (RSHUTDOWN) auf, damit die Erweiterung alle erforderlichen Aufräumarbeiten durchführen kann.

Wie ein Plugin funktioniert

Ein mysqlnd-Plugin funktioniert, indem es die Aufrufe an mysqlnd abfängt, die von Erweiterungen stammen, die mysqlnd verwenden. Dies wird dadurch erreicht, dass die Funktionstabelle von mysqlnd abgerufen, gesichert und durch eine eigene Funktionstabelle ersetzt wird, die die Funktionen des Plugins nach Bedarf aufruft.

Der folgende Code zeigt, wie die Funktionstabelle von mysqlnd ersetzt wird:

/* ein Ort zum Speichern der ursprünglichen Funktionstabelle */
struct st_mysqlnd_conn_methods org_methods;

void minit_register_hooks(TSRMLS_D) {
  /* die aktive Funktionstabelle */
  struct st_mysqlnd_conn_methods * current_methods
    = mysqlnd_conn_get_methods();

  /* Sicherung der ursprünglichen Funktionstabelle */
  memcpy(&org_methods, current_methods,
    sizeof(struct st_mysqlnd_conn_methods);

  /* Installation der neuen Methoden */
  current_methods->query = MYSQLND_METHOD(my_conn_class, query);
}

Die Bearbeitung der Tabelle der Verbindungsfunktionen muss während der Modulinitialisierung (MINIT) erfolgen. Die Funktionstabelle ist eine gemeinsam genutzte globale Ressource. In einer Multi-Thread-Umgebung mit einem TSRM-Build führt die Bearbeitung einer gemeinsam genutzten globalen Ressource während der Verarbeitung einer Anfrage mit ziemlicher Sicherheit zu Konflikten.

Hinweis:

Bei der Bearbeitung der Funktionstabelle von mysqlnd sollte keine Logik mit fester Größe verwendet werden: Neue Methoden könnten am Ende der Funktionstabelle hinzugefügt werden. Die Funktionstabelle kann sich in der Zukunft jederzeit ändern.

Aufrufen von Elternmethoden

Wenn die ursprüngliche Funktionstabelle gesichert wird, können die ursprünglichen Einträge - die Elternmethoden - weiterhin aufgerufen werden.

In einigen Fällen, z. B. bei Connection::stmt_init(), ist es unerlässlich, die Elternmethode aufzurufen, bevor weitere Aktionen in der abgeleiteten Methode erfolgen.

MYSQLND_METHOD(my_conn_class, query)(MYSQLND *conn,
  const char *query, unsigned int query_len TSRMLS_DC) {

  php_printf("my_conn_class::query(query = %s)\n", query);

  query = "SELECT 'query rewritten' FROM DUAL";
  query_len = strlen(query);

  return org_methods.query(conn, query, query_len); /* return with call to parent */
}

Erweitern von Eigenschaften

Ein mysqlnd-Objekt wird durch eine C-Struktur (struct) dargestellt. Es ist nicht möglich, einer C-Struktur zur Laufzeit ein Mitglied hinzuzufügen. Benutzer von mysqlnd-Objekten können nicht einfach Eigenschaften zu den Objekten hinzufügen.

Beliebige Daten (Eigenschaften) können zu einem mysqlnd-Objekt hinzugefügt werden, indem eine geeignete Funktion der mysqlnd_plugin_get_plugin_<object>_data()-Familie verwendet wird. Wenn ein Objekt zugewiesen wird, reserviert mysqlnd am Ende des Objekts Speicherplatz für einen void *-Zeiger auf beliebige Daten. mysqlnd reserviert den Platz für einen void *-Zeiger pro Plugin.

Die folgende Tabelle zeigt, wie die Position des Zeigers für ein bestimmtes Plugin berechnet werden kann:

Berechnen eines Zeigers für mysqlnd
Speicheradresse Inhalt
0 Beginn der C-Struktur des mysqlnd-Objekts
n Ende der C-Struktur des mysqlnd-Objekts
n + (m x sizeof(void*)) void* zu den Objektdaten des m-ten Plugins

Wenn geplant ist, Unterklassen für einen der Konstruktoren des mysqlnd-Objekts zu erstellen, was erlaubt ist, muss dies unbedingt bedacht werden!

Der folgende Code zeigt, wie Eigenschaften erweitert werden:

/* alle Daten, die zugeordnet werden sollen */
typedef struct my_conn_properties {
  unsigned long query_counter;
} MY_CONN_PROPERTIES;

/* die Plugin-ID */
unsigned int my_plugin_id;

void minit_register_hooks(TSRMLS_D) {
  /* erhalten einer eindeutigen Plugin-ID */
  my_plugin_id = mysqlnd_plugin_register();
  /* snip - siehe Extending Connection: methods */
}

static MY_CONN_PROPERTIES** get_conn_properties(const MYSQLND *conn TSRMLS_DC) {
  MY_CONN_PROPERTIES** props;
  props = (MY_CONN_PROPERTIES**)mysqlnd_plugin_get_plugin_connection_data(
    conn, my_plugin_id);
  if (!props || !(*props)) {
    *props = mnd_pecalloc(1, sizeof(MY_CONN_PROPERTIES), conn->persistent);
    (*props)->query_counter = 0;
  }
  return props;
}

Der Plugin-Entwickler ist für die Verwaltung des Speichers verantwortlich, der für die Plugin-Daten verwendet wird.

Es wird empfohlen, für die Daten von Plugins den Speicherallokator von mysqlnd zu verwenden. Diese Funktionen werden nach dem Schema mnd_*loc() benannt. Der mysqlnd-Allokator hat einige nützliche Eigenschaften, zum Beispiel die Möglichkeit, einen Debug-Allokator in einem Nicht-Debug-Build zu benutzen.

Wann und wie eine Unterklasse erstellt wird
  Wann soll die Unterklasse erstellt werden? Hat jede Instanz ihre eine eigene Funktionstabelle? Wie wird die Unterklasse erstellt?
Verbindung (MYSQLND) MINIT Nein mysqlnd_conn_get_methods()
Ergebnismenge (MYSQLND_RES) MINIT oder später Ja mysqlnd_result_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden
Metadaten der Ergebnismenge (MYSQLND_RES_METADATA) MINIT Nein mysqlnd_result_metadata_get_methods()
Anweisung (MYSQLND_STMT) MINIT Nein mysqlnd_stmt_get_methods()
Netzwerk (MYSQLND_NET) MINIT oder später Ja mysqlnd_net_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden
Wire-Protokoll (MYSQLND_PROTOCOL) MINIT oder später Ja mysqlnd_protocol_get_methods() oder Manipulation der Funktionstabelle der Objektmethoden

Nach MINIT darf die Funktionstabelle nicht mehr manipuliert werden, wenn es nach der obigen Tabelle nicht erlaubt ist.

Einige Klassen enthalten einen Zeiger auf die Funktionstabelle der Methoden. Alle Instanzen einer solchen Klasse teilen sich dieselbe Funktionstabelle. Um Chaos zu vermeiden, insbesondere in Umgebungen mit Threads, dürfen solche Funktionstabellen nur während MINIT manipuliert werden.

Andere Klassen verwenden Kopien einer gemeinsam genutzten globalen Funktionstabelle. Die Kopie der Funktionstabelle der Klasse wird zusammen mit dem Objekt erstellt. Jedes Objekt verwendet seine eigene Funktionstabelle. Dadurch ergeben sich zwei Möglichkeiten: Zum einen kann die Standard-Funktionstabelle eines Objekts während MINIT manipuliert werden, zum anderen können die Methoden eines Objekts zusätzlich angepasst werden, ohne dass sich dies auf andere Instanzen derselben Klasse auswirkt.

Der Vorteil der gemeinsam genutzten Funktionstabellen ist die verbesserte Leistung. Das liegt daran, dass es nicht nötig ist, eine Funktionstabelle für jedes einzelne Objekt zu kopieren.

Status des Konstruktors
Typ Zuweisung, Konstruktion, Zurücksetzen Kann geändert werden? Aufgerufen von
Verbindung (MYSQLND) mysqlnd_init() Nein mysqlnd_connect()
Ergebnismenge (MYSQLND_RES)

Zuweisung:

  • Connection::result_init()

Zurücksetzen und Neuinitialisierung während:

  • Result::use_result()

  • Result::store_result

Ja, aber die Elternmethode aufrufen!
  • Connection::list_fields()

  • Statement::get_result()

  • Statement::prepare() (nur Metadaten)

  • Statement::resultMetaData()

Metadaten der Ergebnismenge (MYSQLND_RES_METADATA) Connection::result_meta_init() Ja, aber die Elternmethode aufrufen! Result::read_result_metadata()
Anweisung (MYSQLND_STMT) Connection::stmt_init() Ja, aber die Elternmethode aufrufen! Connection::stmt_init()
Netzwerk (MYSQLND_NET) mysqlnd_net_init() Nein Connection::init()
Wire-Protokoll (MYSQLND_PROTOCOL) mysqlnd_protocol_init() Nein Connection::init()

Es wird dringend empfohlen, einen Konstruktor nicht vollständig zu ersetzen. Die Konstruktoren führen Speicherzuweisungen durch, die für die API des mysqlnd-Plugins und die Objektlogik von mysqlnd unerlässlich sind. Wenn ein Entwickler die Warnungen ignoriert und darauf besteht, die Konstruktoren einzuhängen, sollte er zumindest den übergeordneten Konstruktor aufrufen, bevor er etwas mit dem Konstruktor tut.

Ungeachtet aller Warnungen kann es nützlich sein, Konstruktoren zu vererben. Konstruktoren sind der perfekte Ort, um die Funktionstabellen von Objekten zu ändern, die nicht gemeinsam genutzte Objekttabellen (z. B. Ergebnismenge, Netzwerk, Wire-Protokoll) haben.

Status des Destruktors
Typ Muss die abgeleitete Methode die Elternmethode aufrufen? Destruktor
Verbindung ja, nachdem die Methode ausgeführt wurde free_contents(), end_psession()
Ergebnismenge ja, nachdem die Methode ausgeführt wurde free_result()
Metadaten der Ergebnismenge ja, nachdem die Methode ausgeführt wurde free()
Anweisung ja, nachdem die Methode ausgeführt wurde dtor(), free_stmt_content()
Netzwerk ja, nachdem die Methode ausgeführt wurde free()
Wire-Protokoll ja, nachdem die Methode ausgeführt wurde free()

Der Destruktor ist der geeignete Ort, um Ressourcen freizugeben, die von mysqlnd_plugin_get_plugin_<object>_data()-Eigenschaften belegt sind.

Die aufgeführten Destruktoren entsprechen nicht unbedingt der eigentlichen mysqlnd-Methode, die das Objekt selbst freigibt. Dennoch sind sie der bestmögliche Ort, um die Plugin-Daten einzuhängen und freizugeben. Wie bei den Konstruktoren können die Methoden vollständig ersetzt werden, was jedoch nicht empfohlen wird. Wenn in der obigen Tabelle mehrere Methoden aufgeführt sind, müssen alle aufgeführten Methoden eingehängt und die Plugin-Daten in der Methode freigegeben werden, die von mysqlnd zuerst aufgerufen wird.

Die empfohlene Methode für Plugins ist, einfach die Methoden einzuhängen, den Speicher freizugeben und unmittelbar danach die übergeordnete Implementierung aufzurufen.

add a note

User Contributed Notes

There are no user contributed notes for this page.
To Top