Probleme bei der Verwendung der NtQuerySystemInformation-Funktion mit undokumentierten Argumenten

Der Morgen an diesem Tag begann mit der Tatsache, dass „Wenn“ brach. Dieser Ausdruck wurde einmal von einem meiner Kollegen geprägt, der demonstrierte, wie sein Debugger beim Durchlaufen des Codes in den if-Block gelangt, während die Bedingung, dass das if überprüft wurde, genau falsch war. Das Problem stellte sich damals als trivial heraus - er verwendete einen Release-optimierten Build, und in diesem Szenario ist das Vertrauen in das schrittweise Debuggen natürlich unmöglich. Aber der Ausdruck „wenn gebrochen“ hat Wurzeln geschlagen und wird seitdem hier verwendet, um auf eine Situation hinzuweisen, in der etwas so Grundlegendes nicht mehr funktioniert, dass es kaum noch daran geglaubt wurde.

An diesem Tag brach die NtQuerySystemInformation- Funktion zusammen - eine der wichtigsten Funktionen des Windows-Betriebssystems, die Informationen zu Prozessen, Threads, Systemdeskriptoren usw. zurückgibt. Über die Vorteile dieser Funktion habe ich diesen Artikel einmal geschrieben. Es stellte sich jedoch heraus, dass selbst solche Eckpfeiler eines Systems manchmal versagen können.

Also, was ist passiert?

Lange Zeit (bereits seit einigen Jahren) haben wir den Aufruf der Funktion NtQuerySystemInformation mit dem Argument SystemHandleInformation verwendet, um Informationen zu allen Deskriptoren im System abzurufen. Ja, dieses Argument bezieht sich formal auf undokumentierte Argumente. Wenn Sie jedoch nach Informationen zum Auflisten aller Deskriptoren in allen derzeit ausgeführten Windows-Anwendungen suchen, ist die Kombination aus NtQuerySystemInformation + SystemHandleInformation die am häufigsten vorgeschlagene Option. Und es funktioniert wirklich auf allen Betriebssystemen, die mit Windows NT beginnen.

Warum müssen Sie möglicherweise in allen Prozessen nach Deskriptoren suchen? Nun, aus verschiedenen Gründen. Dienstprogramme wie Process Hacker zeigen sie nur zu Informationszwecken an. Es gibt Programme, die dies tun, um nach einer Ressource zu suchen, die derzeit von jemandem blockiert wird (z. B. einer Datei). Sie können beispielsweise einen Mutex im Prozess einer anderen Person finden, mit dem nur eine Kopie des Programms ausgeführt, geschlossen und zwei Instanzen einer solchen Anwendung gestartet werden können. Oder listen Sie Deskriptoren auf, um sie zu duplizieren und die Sandbox zu organisieren. Im Allgemeinen gibt es viele Aufgaben.

Ich werde hier nicht die vollständige Beschreibung der Deskriptor-Aufzählung geben, sondern nur sagen, dass sie im Allgemeinen gängigen Beispielen wie diesen ähnlich war:

while ((status = NtQuerySystemInformation( SystemHandleInformation, handleInfo, handleInfoSize, NULL )) == STATUS_INFO_LENGTH_MISMATCH) handleInfo = (PSYSTEM_HANDLE_INFORMATION)realloc(handleInfo, handleInfoSize *= 2); // NtQuerySystemInformation stopped giving us STATUS_INFO_LENGTH_MISMATCH. if (!NT_SUCCESS(status)) { printf("NtQuerySystemInformation failed!\n"); return 1; } for (i = 0; i < handleInfo->HandleCount; i++) { ... } 

Aber dann starte ich unsere Anwendung - und plötzlich stellt sich heraus, dass der Deskriptor, den ich brauche (und ich weiß sicher, dass er existiert!), Nicht in der Liste enthalten ist, die von der Funktion NtQuerySystemInformation () zurückgegeben wird. Das war's, sie kamen - "wenn es kaputt ist".

Der Versuch, das Problem auf anderen Computern im Büro zu reproduzieren. Bei einigen wird es reproduziert, bei der Mehrheit nicht. Wir versuchen zu verstehen, wie sich diejenigen, auf denen es reproduziert, von denen unterscheiden, auf denen alles gut ist. Die Windows-Version ist überall gleich, Updates, der Build unseres Programms - alles ist identisch. Plötzlich bemerkt jemand, dass alle Laptops, auf denen das Problem reproduziert wurde, vom selben Modell sind. Hardware-Inkompatibilität? Aber warum funktionierte es jetzt plötzlich ... Außerdem gibt es im Büro andere Laptops des gleichen Modells, die jetzt funktionieren. Sogar die Versionen der Gerätetreiber wurden verglichen - alles scheint gleich zu sein. Aber auf einigen Laptops funktioniert alles, auf anderen nicht.

Das Haarziehen am Kopf dauerte ungefähr einen halben Tag, bis ich versehentlich zwei Dinge bemerkte:

  1. Die Prozess-PIDs, bei denen es sich aus irgendeinem Grund normalerweise um drei-, vier- oder fünfstellige Zahlen auf meinem Computer handelt, sind sechsstellig geworden. Es war seltsam genug, eine PID vom Typ 780936 zu sehen. Diese hatte ich vorher noch nicht bemerkt. Darüber hinaus war die Gesamtzahl der laufenden Prozesse völlig ausreichend (bis zu hundert).
  2. Der Task-Manager auf der Registerkarte CPU zeigte die Gesamtzahl der Deskriptoren im System an - und es war riesig, mehr als 800.000.

Für eine normale Anwendung ist es normal, hundert oder zwei Deskriptoren zu öffnen. Nun, tausend. Bei aktiver Nutzung kann Chrome etwa 2000 öffnen, Visual Studio kann 3000 bei großen Projekten öffnen. Aber wer hat 800 000 geöffnet? Glücklicherweise können Sie mit dem zuvor erwähnten Process Hacker die Anzahl der Deskriptoren für jeden Prozess anzeigen und sogar die Liste der Prozesse nach der Anzahl der verwendeten Deskriptoren sortieren.

Und was sehen wir? Und wir sehen so etwas wie dieses Bild:



Ich muss sagen, dass ich gerade den obigen Screenshot gemacht habe, also hat der erste in der Prozessliste "insgesamt" ungefähr 20.000 Deskriptoren. Und als ich das Problem zum ersten Mal sah, waren es ungefähr 650.000. Und wer ist unser Held? Bingo! Dies ist der Prozess SynTPEnhService.exe.

Und dann entwickelt sich das ganze Rätsel in meinem Kopf. SynTPEnhService.exe ist Teil des Synaptics-Touchpad-Treibers. Es wurde nur auf Laptops eines bestimmten Modells in unserem Büro installiert, auf denen das Problem aufgetreten ist. Eine kurze Beobachtung ergab, dass dieser Prozess alle 5 Sekunden den untergeordneten Prozess SynTPEnh.exe startet, der nach 1-2 Sekunden geschlossen wird. Gleichzeitig enthält der übergeordnete Prozess weiterhin den Deskriptor des untergeordneten Prozesses, was zum Verlust von Deskriptoren führt. Einer nach dem anderen alle 5 Sekunden. Dies sind 17.280 Deskriptoren pro Tag. Lassen Sie den Computer eine Woche lang eingeschaltet, und jetzt haben Sie mehr als hunderttausend Hang-Deskriptoren. Mein PC wurde länger als einen Monat nicht neu gestartet - daher die PIDs neuer Prozesse mit Zahlen über einer halben Million. Dies erklärt auch, warum das Problem auf einigen Laptops in unserem Büro reproduziert wurde, auf den anderen Laptops jedoch nicht: Einige meiner Kollegen haben ihre PCs jeden Tag neu gestartet, und jemand wie ich hat sie für die Nacht eingeschaltet gelassen .

An dieser Stelle fiel mir übrigens ein, dass ich bereits über ein Problem mit den Synaptics-Touchpad-Treibern gelesen hatte. Nach einigem Graben fand ich diesen Artikel von Bruce Dawson (viele Übersetzungen seiner Artikel wurden zu unterschiedlichen Zeiten auf Habré veröffentlicht, aber nicht dieser spezifische). Dort beschreibt er das Problem des Speicherverlusts aufgrund dieses endlosen Neustarts des SynTPEnh.exe-Prozesses, sagt jedoch nichts über das Problem des Handle-Lecks aus, sodass sich mein Fund immer noch davon unterscheidet.

Lösung


Der Touchpad-Treiber "frisst" Hunderttausende von Deskriptoren - und was nun? Und die Tatsache, dass die Funktion NtQuerySystemInformation (SystemHandleInformation, ...), die in den Tagen von Windows NT geschrieben wurde, einen recht begrenzten internen Puffer hatte (und hat). Ich konnte nirgendwo einen genauen Hinweis auf seine Größe finden, aber offensichtlich war es nicht für eine Million Deskriptoren ausgelegt. Infolgedessen gibt die Funktion sie "so viel wie möglich" zurück, was bedeutet, dass unter ihnen möglicherweise die gewünschte vorhanden ist oder nicht.

Was tun? Wie Rick aus der Zeichentrickserie „Rick and Morty“ sagte: „Wenn Sie die Teleportation erfinden, entdecken Sie sofort eine unangenehme Sache: Sie sind die letzte Person im Universum, die sie erfunden hat.“ Wie sich herausstellte, erkannte Microsoft dieses Problem mit dem begrenzten Puffer in NtQuerySystemInformation, als es vor 20 Jahren mit dem Argument SystemHandleInformation aufgerufen wurde. Daher fügten sie ab WindowsXP der Funktion NtQuerySystemInformation ein weiteres (und auch nicht dokumentiertes) Argument SystemExtendedHandleInformation hinzu. Wenn Sie NtQuerySystemInformation (SystemExtendedHandleInformation, ...) aufrufen, werden alle Deskriptoren im System an Sie zurückgegeben, unabhängig davon, wie viele es gibt. Nun, oder besser gesagt, ich weiß das nicht genau, vielleicht gibt es einige Einschränkungen für dieses Argument, aber es ist sicher, dass er 800.000 Deskriptoren in einem Zustand zurückgeben kann.

Im Internet finden Sie Beispiele für die Verwendung von SystemExtendedHandleInformation, z. B. dieses . Im Allgemeinen ist dort alles ähnlich, andere Strukturen werden einfach verwendet, und das ist alles.

Es war eine lehrreiche Geschichte über die Verwendung von undokumentierten Argumenten des Widnows-Betriebssystems, die sehr nützlich sein kann, aber sorgfältige Tests und die Vorbereitung auf nicht standardmäßige Probleme erfordert.

Source: https://habr.com/ru/post/de433906/


All Articles