Problèmes d'utilisation de la fonction NtQuerySystemInformation avec des arguments non documentés

La matinée du jour a commencé par le fait que les «si» se sont rompus. Cette expression a été inventée une fois par l'un de mes collègues qui a montré comment son débogueur entre dans le bloc if lors de la navigation dans le code, alors que la condition que le si vérifié était exactement faux. Le problème à ce moment-là s'est avéré être trivial - il a utilisé une version optimisée pour la version, et dans ce scénario, la confiance dans le débogage étape par étape, bien sûr, est impossible. Mais l'expression même «ifs cassé» a pris racine et a été utilisée ici depuis lors pour indiquer une situation où quelque chose de si fondamental a cessé de fonctionner qu'on y croyait à peine.

Donc, ce jour-là, la fonction NtQuerySystemInformation est tombée en panne - l'une des fonctions les plus importantes du système d'exploitation Windows, qui renvoie des informations sur les processus, les threads, les descripteurs système, etc. Sur les avantages de l'utilisation de cette fonctionnalité, j'ai déjà écrit cet article . Mais il s'est avéré que même ces pierres angulaires d'un système peuvent parfois échouer.

Alors qu'est-ce qui s'est passé.

Pendant assez longtemps (pendant plusieurs années), nous avons utilisé l'appel à la fonction NtQuerySystemInformation avec l'argument SystemHandleInformation pour obtenir des informations sur tous les descripteurs du système. Oui, cet argument fait officiellement référence à ceux non documentés, mais si vous commencez à rechercher des informations sur la façon de répertorier tous les descripteurs dans toutes les applications Windows OS en cours d'exécution, la combinaison NtQuerySystemInformation + SystemHandleInformation sera l'option la plus souvent proposée. Et cela fonctionne vraiment, sur tous les systèmes d'exploitation à partir de Windows NT.

Pourquoi pourriez-vous avoir besoin de rechercher des descripteurs dans tous les processus? Eh bien, pour diverses raisons. Des utilitaires comme Process Hacker les affichent uniquement à titre informatif. Il existe des programmes qui le font afin de rechercher une ressource qui est actuellement bloquée par quelqu'un (par exemple, un fichier). Et vous pouvez, par exemple, trouver dans un processus étranger un mutex utilisé pour autoriser le lancement d'une seule copie du programme, le fermer et autoriser le lancement de deux instances d'une telle application. Ou listez les descripteurs pour les dupliquer afin d'organiser le sandbox. En général, il existe de nombreuses tâches.

Je ne donnerai pas ici la description complète de l'énumération des descripteurs, je dirai simplement qu'elle était, en général, similaire à des exemples courants, comme celui-ci :

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++) { ... } 

Mais ensuite je lance notre application - et soudain, il s'avère que le descripteur dont j'ai besoin (et je sais avec certitude qu'il existe!) Ne figure pas dans la liste renvoyée par la fonction NtQuerySystemInformation (). Ça y est, ils sont venus - "si c'est cassé".

Essayer de reproduire le problème sur d'autres ordinateurs du bureau. Sur certains, il est reproduit, sur la majorité - non. Nous essayons de comprendre en quoi ceux sur lesquels il se reproduit diffèrent de ceux sur lesquels tout va bien. La version Windows est la même partout, les mises à jour, la construction de notre programme - tout est identique. Du coup, quelqu'un remarque que tous les ordinateurs portables sur lesquels le problème a été reproduit sont du même modèle. Incompatibilité matérielle? Mais pourquoi tout à coup maintenant, ça fonctionnait ... De plus, il y a d'autres ordinateurs portables du même modèle dans le bureau qui fonctionnent maintenant. Même les versions des pilotes de périphériques ont été comparées - tout semble être le même. Mais tout fonctionne sur certains ordinateurs portables, mais pas sur d'autres.

Tirer les cheveux sur la tête a duré environ une demi-journée, jusqu'à ce que je remarque accidentellement deux choses:

  1. Les PID de processus, qui sont généralement des nombres à trois, quatre ou cinq chiffres sur mon ordinateur pour une raison quelconque, sont devenus à six chiffres. C'était assez étrange de voir un PID de type 780936. Je ne les avais pas remarqués auparavant. De plus, le nombre total de processus en cours d'exécution était tout à fait suffisant (jusqu'à une centaine).
  2. Le gestionnaire de tâches sur l'onglet CPU a montré le nombre total de descripteurs dans le système - et c'était énorme, plus de 800 000.

Pour une application normale, il est normal d'ouvrir cent ou deux descripteurs. Eh bien, mille. Avec une utilisation active, Chrome peut ouvrir environ 2000, Visual Studio peut en ouvrir 3000 sur de grands projets, mais qui a ouvert 800 000? Heureusement, le Process Hacker mentionné précédemment vous permet d'afficher le nombre de descripteurs pour chaque processus et même de trier la liste des processus en fonction du nombre de descripteurs utilisés.

Et que voyons-nous? Et nous voyons quelque chose comme cette image:



Je dois dire que je viens de faire la capture d'écran ci-dessus, de sorte que le premier de la liste de processus n'a «que» environ 20 000 descripteurs. Et puis, quand j'ai vu le problème pour la première fois, il y en avait environ 650 000. Et qui est notre héros? Bingo! Il s'agit du processus SynTPEnhService.exe.

Et puis tout le puzzle se développe dans ma tête. SynTPEnhService.exe fait partie du pilote du pavé tactile Synaptics. Il a été installé uniquement sur les ordinateurs portables d'un certain modèle dans notre bureau, sur lesquels le problème s'est produit. Une brève observation a montré que toutes les 5 secondes, ce processus démarre le processus enfant SynTPEnh.exe, qui se ferme après 1-2 secondes. Dans le même temps, le processus parent continue de contenir le descripteur du processus enfant, ce qui entraîne une fuite de descripteurs. Un à la fois toutes les 5 secondes. C'est 17 280 descripteurs par jour. Laissez l'ordinateur allumé pendant une semaine et vous avez maintenant plus de cent mille descripteurs de blocage. Mon ordinateur personnel n'a pas redémarré pendant plus d'un mois - d'où les PID des nouveaux processus avec des nombres supérieurs à un demi-million. Cela explique également pourquoi le problème a été reproduit sur certains ordinateurs portables dans notre bureau, mais il ne s'est pas produit sur les autres ordinateurs portables: certains de mes collègues ont redémarré leur PC tous les jours et quelqu'un, comme moi, les a laissés allumés pour la nuit. .

À propos, à cet endroit, je me suis souvenu que j'avais déjà lu un problème avec les pilotes du pavé tactile Synaptics. Après avoir creusé un peu, j'ai trouvé cet article écrit par Bruce Dawson (de nombreuses traductions de ses articles ont été publiées à différents moments sur Habré, mais pas celle-ci en particulier). Là, il décrit le problème de fuite de mémoire dû à ce redémarrage sans fin du processus SynTPEnh.exe, mais ne dit rien sur le problème de fuite de poignée, donc ma découverte est toujours différente de celle-ci.

Résolution de problèmes


Ainsi, le pilote du pavé tactile "mange" des centaines de milliers de descripteurs - et alors quoi? Et le fait que la fonction NtQuerySystemInformation (SystemHandleInformation, ...) écrite à l'époque de Windows NT avait (et a) un tampon interne assez limité. Je n'ai pu trouver une indication exacte de sa taille nulle part, mais, évidemment, il n'a pas été conçu pour un million de descripteurs. Par conséquent, la fonction les renvoie «autant que possible», ce qui signifie que parmi eux, il peut y avoir ou non celui souhaité.

Que faire? Comme Rick de la série animée "Rick et Morty" l'a dit: "Lorsque vous inventez la téléportation, vous découvrez immédiatement une chose désagréable: vous êtes la dernière personne de l'univers à l'avoir inventée." Il s'est avéré que Microsoft a réalisé ce problème avec le tampon limité dans NtQuerySystemInformation en l'appelant avec l'argument SystemHandleInformation il y a 20 ans, et donc, à partir de WindowsXP, ils ont ajouté la fonction NtQuerySystemInformation un autre argument (et également non documenté) SystemExtendedHandleInformation. Lorsque vous appelez NtQuerySystemInformation (SystemExtendedHandleInformation, ...), tous les descripteurs du système vous seront retournés, quel que soit leur nombre. Eh bien, ou plutôt, je ne le sais pas avec certitude, il y a peut-être des restrictions pour cet argument, mais c'est sûr qu'il peut retourner 800 000 descripteurs dans un état.

Sur le net, vous pouvez trouver des exemples d'utilisation de SystemExtendedHandleInformation, par exemple, celui-ci . En général, tout est similaire là-bas, d'autres structures sont simplement utilisées, et c'est tout.

C'était une histoire instructive sur l'utilisation d'arguments non documentés du système d'exploitation Widnows, qui peuvent être très utiles, mais nécessitent des tests minutieux et une préparation aux problèmes non standard.

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


All Articles