Sous le cutter se trouve la traduction de la première partie du document Detecting Kernel Memory Disclosure with x86 Emulation and Taint Tracking ( Article Project Zero ) de Mateusz Jurczyk .
Dans la partie traduite du document:
- Spécificités du langage de programmation C (dans le cadre du problème d'extension de mémoire)
- les spécificités du fonctionnement des noyaux Windows et Linux (dans le cadre du problème d'extension mémoire)
- importance de la divulgation de la mémoire du noyau et impact sur la sécurité du système d'exploitation
- méthodes et techniques existantes pour détecter et contrer la divulgation de la mémoire du noyau
Bien que le document se concentre sur les mécanismes de communication entre le noyau privilégié du système d'exploitation et les applications utilisateur, l'essence du problème peut être généralisée pour tout transfert de données entre différents domaines de sécurité: l'hyperviseur est la machine invitée, le service système privilégié (démon) est l'application graphique, le client réseau est le serveur, etc. .

Présentation
L'une des tâches des systèmes d'exploitation modernes est d'assurer la séparation des privilèges entre les applications utilisateur et le noyau du système d'exploitation. Premièrement, cela inclut le fait que l'influence de chaque programme sur l'exécution doit être limitée par une certaine politique de sécurité, et deuxièmement, que les programmes ne peuvent accéder qu'aux informations qu'ils sont autorisés à lire. Le second est difficile à fournir, étant donné les propriétés du langage C (le langage de programmation principal utilisé dans le développement du noyau), qui rendent extrêmement difficile le transfert sécurisé des données entre différents domaines de sécurité.
Les systèmes d'exploitation modernes fonctionnant sur des plates-formes x86 / x86-64 sont multithread et utilisent un modèle client-serveur dans lequel les applications en mode utilisateur (clients) sont exécutées indépendamment et appellent le noyau du système d'exploitation (serveur) avec l'intention de travailler avec une ressource gérée par le système. Le mécanisme utilisé par le code du mode utilisateur ( anneau 3 ) pour appeler un ensemble prédéfini de fonctions du noyau (anneau 0) est appelé appels système ou (brièvement) appels système. Un appel système typique est illustré à la figure 1:

Figure 1: cycle de vie des appels système.
Il est très important d'éviter de fuir par inadvertance le contenu de la mémoire du noyau lors de l'interaction avec des programmes en mode utilisateur. Il existe un risque important de divulgation de données sensibles du noyau. Les données peuvent être implicitement transmises dans les paramètres de sortie des appels système sûrs (à d'autres points de vue).
La divulgation de la mémoire système privilégiée se produit lorsque le noyau du système d'exploitation renvoie une région de mémoire plus grande (excédentaire) que nécessaire pour stocker les informations correspondantes (contenues à l'intérieur). Souvent, les octets redondants contiennent des données qui ont été remplies dans un contexte différent, puis la mémoire n'a pas été pré-initialisée, ce qui empêcherait la diffusion d'informations dans de nouvelles structures de données.
Spécificités du langage de programmation C
Dans cette section, nous examinons plusieurs aspects du langage C qui sont les plus importants pour le problème d'extension de mémoire.
État indéfini de variables non initialisées
Les variables individuelles de types simples (tels que char ou int), ainsi que les membres des structures de données (tableaux, structures et unions) restent dans un état indéfini jusqu'à la première initialisation (qu'ils soient placés sur la pile ou sur le tas). Citations pertinentes de la spécification C11 (ISO / IEC 9899: 201x Draft de comité N1570, avril 2011):
6.7.9 Initialisation
...
10 Si un objet qui a une durée de stockage automatique n'est pas initialisé explicitement, sa valeur est indéterminée .
7.22.3.4 La fonction malloc
...
2 La fonction malloc alloue de l'espace à un objet dont la taille est spécifiée par la taille et dont la valeur est indéterminée .
7.22.3.5 La fonction realloc
...
2 La fonction realloc désalloue l'ancien objet pointé par ptr et renvoie un pointeur sur un nouvel objet dont la taille est spécifiée par taille. Le contenu du nouvel objet doit être le même que celui de l'ancien objet avant la désallocation, jusqu'à la moindre des tailles nouvelle et ancienne. Tous les octets du nouvel objet au-delà de la taille de l'ancien objet ont des valeurs indéterminées .
La partie qui s'applique au code système est la plus pertinente pour les objets situés sur la pile, car le noyau du système d'exploitation a généralement des interfaces d'allocation dynamique avec leur propre sémantique (pas nécessairement compatible avec la bibliothèque C standard, comme cela sera décrit plus loin).
À notre connaissance, aucun des trois compilateurs C les plus populaires pour Windows et Linux (compilateur Microsoft C / C ++, gcc, LLVM) ne crée de code qui pré-initialise les variables non initialisées par le programmeur sur la pile en mode Release-build (ou son équivalent). Il existe des options de compilation pour marquer les cadres de pile avec des octets spéciaux - marqueurs (/ RTC dans Microsoft Visual Studio, par exemple) mais ils ne sont pas utilisés dans les versions Release pour des raisons de performances. Par conséquent, les variables non initialisées de la pile héritent des anciennes valeurs des zones de mémoire correspondantes.
Prenons un exemple d'implémentation standard d'un appel système fictif Windows qui multiplie un entier d'entrée par deux et renvoie le résultat de la multiplication (Listing 1). Évidemment, dans le cas spécial (InputValue == 0), la variable OutputValue reste non initialisée et est recopiée sur le client. Cette erreur vous permet d'ouvrir quatre octets de mémoire de pile de noyau pour chaque appel.
NTSTATUS NTAPI NtMultiplyByTwo(DWORD InputValue, LPDWORD OutputPointer) { DWORD OutputValue; if (InputValue != 0) { OutputValue = InputValue * 2; } *OutputPointer = OutputValue; return STATUS_SUCCESS; }
Exemple de code 1: étendre la mémoire via une variable locale non initialisée.
Les fuites via une variable locale non initialisée ne sont pas très courantes dans la pratique: d'une part, les compilateurs modernes détectent et avertissent souvent de tels problèmes, d'autre part, ces fuites sont des erreurs fonctionnelles qui peuvent être détectées lors du développement ou des tests. Cependant, le deuxième exemple (dans le Listing 2) montre qu'une fuite peut également se produire via le champ de structure.
Dans ce cas, le champ de structure réservé n'est jamais explicitement utilisé dans le code, mais est toujours copié en mode utilisateur et, par conséquent, expose également quatre octets de mémoire du noyau au code appelant. Cet exemple montre clairement que l'initialisation de chaque champ de chaque structure retournée au client pour toutes les branches d'exécution de code n'est pas une tâche facile. Dans de nombreux cas, l'initialisation forcée semble illogique, surtout si ce champ ne joue aucun rôle pratique. Mais c'est le fait qu'une variable (ou champ de structure) non initialisée sur la pile (ou sur le tas) accepte le contenu des données précédemment stockées dans cette zone mémoire (dans le cadre d'une autre opération), se situe au cœur du problème d'extension mémoire du noyau.
typedef struct _SYSCALL_OUTPUT { DWORD Sum; DWORD Product; DWORD Reserved; } SYSCALL_OUTPUT, *PSYSCALL_OUTPUT; NTSTATUS NTAPI NtArithOperations( DWORD InputValue, PSYSCALL_OUTPUT OutputPointer ) { SYSCALL_OUTPUT OutputStruct; OutputStruct.Sum = InputValue + 2; OutputStruct.Product = InputValue * 2; RtlCopyMemory(OutputPointer, &OutputStruct, sizeof(SYSCALL_OUTPUT)); return STATUS_SUCCESS; }
Listing 2: Extension de mémoire via un champ de structure réservé.
Alignement des structures et des octets de remplissage
L'initialisation de tous les champs de la structure de sortie est un bon début pour éviter d'étendre la mémoire. Mais cela ne suffit pas pour garantir que dans la représentation de bas niveau, il n'y a pas d'octets non initialisés. Revenons à la spécification C11:
6.5.3.4 Les opérateurs sizeof et Alignof
...
4 [...] Lorsqu'il est appliqué à un opérande qui a une structure ou un type d'union, le résultat est le nombre total d'octets dans un tel objet, y compris le remplissage interne et final .
6.2.8 Alignement d'objets
1 Les types d'objet complets ont des exigences d'alignement qui imposent des restrictions sur les adresses auxquelles les objets de ce type peuvent être attribués . Un alignement est une valeur entière intégrée définie par l'implémentation représentant le nombre d'octets entre des adresses successives auxquelles un objet donné peut être alloué. [...]
6.7.2.1 Structure et spéci fi cateurs d'union
...
17 Il peut y avoir un rembourrage sans nom à la fin d'une structure ou d'union .
Autrement dit, les compilateurs en langage C pour les architectures x86 (-64) utilisent l'alignement naturel des champs de structure (ayant un type primitif): chacun de ces champs est aligné de N octets, où N est la taille du champ. En outre, des structures et des jointures entières sont également alignées lorsqu'elles sont déclarées dans un tableau et l'exigence d'alignement des champs imbriqués est satisfaite. Pour garantir l'alignement, des octets de remplissage implicites sont insérés dans les structures si nécessaire. Bien qu'ils ne soient pas directement accessibles dans le code source, ces octets héritent également des anciennes valeurs des zones de mémoire et peuvent transmettre des informations au mode utilisateur.
Dans l'exemple du Listing 3, la structure SYSCALL_OUTPUT est renvoyée au code appelant. Il contient des champs de 4 et 8 octets, séparés par 4 octets de remplissage, nécessaires pour que l'adresse du champ LargeSum devienne un multiple de 8. Malgré le fait que les deux champs sont correctement initialisés, les octets de remplissage ne sont pas définis explicitement, ce qui conduit à nouveau à l'expansion de la mémoire de la pile du noyau. L'emplacement spécifique de la structure en mémoire est illustré à la figure 2.
typedef struct _SYSCALL_OUTPUT { DWORD Sum; QWORD LargeSum; } SYSCALL_OUTPUT, *PSYSCALL_OUTPUT; NTSTATUS NTAPI NtSmallSum( DWORD InputValue, PSYSCALL_OUTPUT OutputPointer ) { SYSCALL_OUTPUT OutputStruct; OutputStruct.Sum = InputValue + 2; OutputStruct.LargeSum = 0; RtlCopyMemory(OutputPointer, &OutputStruct, sizeof(SYSCALL_OUTPUT)); return STATUS_SUCCESS; }
Listing 3: étendre la mémoire en alignant la structure.

Figure 2: Représentation de la structure en mémoire avec alignement en tête.
Les fuites par les alignements sont relativement courantes, car un grand nombre de paramètres de sortie des appels système sont représentés par des structures. Le problème est particulièrement aigu pour les plates-formes 64 bits, où la taille des pointeurs, size_t et types similaires augmente de 4 à 8 octets, ce qui entraîne l'apparition du remplissage nécessaire pour aligner les champs de ces structures.
Étant donné que les octets de remplissage ne peuvent pas être adressés dans le code source, il est nécessaire d'utiliser memset ou une fonction similaire pour réinitialiser toute la zone mémoire de la structure avant d'initialiser l'un de ses champs et de le copier en mode utilisateur, par exemple:
memset(&OutputStruct, 0, sizeof(OutputStruct));
Cependant, Seacord RC dans son livre "The CERT C Coding Standard, Second Edition: 98 Rules for Developing Safe, Reliable, and Secure Systems. Addison-Wesley Professional" 2014 indique que ce n'est pas une solution idéale parce que les octets de remplissage ) peut encore être supprimé après avoir appelé memset, par exemple, comme effet secondaire des opérations avec des champs adjacents. La préoccupation peut être justifiée par l'énoncé suivant dans la spécification C:
6.2.6 Représentations des types
6.2.6.1 Général
...
6 Lorsqu'une valeur est stockée dans un objet de type structure ou union , y compris dans un objet membre, les octets de la représentation d'objet qui correspondent à tout octet de remplissage prennent des valeurs non spécifiées . [...]
Cependant, dans la pratique, aucun des compilateurs C que nous avons testés n'a lu ou écrit en dehors des zones de mémoire des champs explicitement déclarés. Il semble que cette opinion soit partagée par les développeurs de systèmes d'exploitation qui utilisent memset.
Unions et champs de différentes tailles
Les jointures sont une autre construction complexe du langage C dans le contexte de la communication avec du code d'appel moins privilégié. Considérez comment la spécification C11 décrit la représentation des unions en mémoire:
6.2.5 Types
...
20 Un certain nombre de types dérivés peuvent être construits à partir des types d'objet et de fonction, comme suit: [...] Un type d'union décrit un ensemble non vide chevauchant d'objets membres , chacun ayant un nom éventuellement spécifié et un type éventuellement distinct.
6.7.2.1 Structure et spéci fi cateurs d'union
...
6 Comme indiqué au 6.2.5, une structure est un type composé d'une séquence de membres, dont le stockage est alloué dans une séquence ordonnée, et une union est un type composé d'une séquence de membres dont le stockage se chevauche .
...
16 La taille d'un syndicat est suffisante pour contenir le plus grand de ses membres . La valeur d'au plus un des membres peut être stockée à tout moment dans un objet union.
Le problème est que si l'union se compose de plusieurs champs de tailles différentes et qu'un seul champ de taille plus petite est explicitement initialisé, les octets restants alloués pour prendre en charge les grands champs restent non initialisés. Examinons un exemple de gestionnaire d'appels système hypothétique, illustré dans le listing 4, ainsi que l'allocation de mémoire d'union SYSCALL_OUTPUT illustrée dans la figure 3.
typedef union _SYSCALL_OUTPUT { DWORD Sum; QWORD LargeSum; } SYSCALL_OUTPUT, *PSYSCALL_OUTPUT; NTSTATUS NTAPI NtSmallSum( DWORD InputValue, PSYSCALL_OUTPUT OutputPointer ) { SYSCALL_OUTPUT OutputStruct; OutputStruct.Sum = InputValue + 2; RtlCopyMemory(OutputPointer, &OutputStruct, sizeof(SYSCALL_OUTPUT)); return STATUS_SUCCESS; }
Exemple de code 4: étendre la mémoire en initialisant partiellement une union.

Figure 3: Représentation de l'union en mémoire avec alignement.
Il s'avère que la taille totale de l'union SYSCALL_OUTPUT est de 8 octets (en raison de la taille du plus grand champ LargeSum). Cependant, la fonction définit uniquement la valeur du champ plus petit, laissant 4 octets de fin non initialisés, ce qui entraîne par la suite une fuite vers leur application cliente.
Une implémentation sécurisée doit uniquement définir le champ Sum dans l'espace d'adressage utilisateur et ne pas copier l'intégralité de l'objet avec des zones de mémoire potentiellement inutilisées. Un autre correctif consiste à appeler la fonction memset pour annuler une copie de l'union dans la mémoire du noyau avant de définir l'un de ses champs et de le retransférer en mode utilisateur.
Taille dangereuse
Comme indiqué dans les deux sections précédentes, l'utilisation de l'opérateur sizeof peut contribuer directement ou indirectement à révéler la mémoire du noyau, provoquant la copie de plus de données que précédemment initialisées.
C ne possède pas l'appareil nécessaire pour transférer en toute sécurité des données du noyau vers l'espace utilisateur - ou, plus généralement, entre différents contextes de sécurité. Le langage ne contient pas de métadonnées d'exécution qui peuvent indiquer explicitement quels octets ont été définis dans chaque structure de données utilisée pour interagir avec le noyau du système d'exploitation. En conséquence, la responsabilité incombe au programmeur, qui doit déterminer quelles parties de chaque objet doivent être transmises au code appelant. Si cela est fait correctement, vous devez écrire une fonction de copie sécurisée distincte pour chaque structure de sortie utilisée dans les appels système. Ce qui, à son tour, entraînera un gonflement de la taille du code, une détérioration de sa lisibilité et, en général, sera une tâche fastidieuse et longue.
D'un autre côté, il est pratique et simple de copier toute la zone mémoire du noyau avec un seul appel memcpy et l'argument sizeof, et de laisser le client déterminer quelles parties de la sortie seront utilisées. Il s'avère que cette approche est utilisée aujourd'hui sous Windows et Linux. Et lorsqu'un cas spécifique de fuite d'informations est détecté, un correctif avec un appel memset est immédiatement fourni et distribué par le fabricant du système d'exploitation. Malheureusement, cela ne résout pas le problème dans le cas général.
Spécificités du système d'exploitation
Il existe certaines solutions de conception du noyau, méthodes de programmation et modèles de code qui affectent la vulnérabilité du système d'exploitation aux vulnérabilités d'extension de mémoire. Ils sont examinés dans les sous-sections suivantes.
Réutilisation de la mémoire dynamique
Les allocateurs actuels de mémoire dynamique (en mode utilisateur et en mode noyau) sont hautement optimisés, car leurs performances ont un impact significatif sur les performances de l'ensemble du système. L'une des optimisations les plus importantes est la réutilisation de la mémoire: lorsqu'elle est libérée, la mémoire correspondante est rarement complètement supprimée, mais elle est enregistrée dans la liste des régions prêtes à être renvoyées la prochaine fois qu'elle est allouée. Pour économiser les cycles CPU, les zones de mémoire par défaut ne sont pas effacées entre la désallocation et la nouvelle allocation. À la suite de cela, il s'avère que deux parties non connectées du noyau fonctionnent avec la même plage de mémoire pendant une courte période. Cela signifie que la fuite du contenu de la mémoire dynamique du noyau vous permet de révéler les données de divers composants du système d'exploitation.
Dans les paragraphes suivants, nous donnons un bref aperçu des allocateurs utilisés dans les noyaux Windows et Linux, et leurs qualités les plus remarquables.
Windows
La fonction clé du gestionnaire de pool de noyau Windows est ExAllocatePoolWithTag , qui peut être appelée directement ou via l'un des shells disponibles: ExAllocatePool {∅, Ex, WithQuotaTag, WithTagPriority}. Aucune de ces fonctions ne vide le contenu de la mémoire retournée, par défaut ou via des drapeaux d'entrée. Au contraire, ils ont tous l'avertissement suivant dans leur documentation MSDN respective:
Remarque La mémoire allouée par la fonction n'est pas initialisée. Un pilote en mode noyau doit d'abord mettre à zéro cette mémoire s'il veut la rendre visible aux logiciels en mode utilisateur (pour éviter de divulguer des contenus potentiellement privilégiés).
Le code appelant peut sélectionner l'un des six principaux types de pools: NonPagedPool, NonPagedPoolNx, NonPagedPoolSession, NonPagedPoolSessionNx, PagedPool et PagedPoolSession. Chacun d'eux a une région distincte dans l'espace d'adressage virtuel, et donc les zones de mémoire allouées ne peuvent être réutilisées que dans le même type de pool. La fréquence de réutilisation des morceaux de mémoire est très élevée et les zones mises à zéro ne sont généralement renvoyées que si un enregistrement approprié n'est pas trouvé dans les listes de côté ou si la demande est si grande que de nouvelles pages de mémoire sont nécessaires. En d'autres termes, il n'existe actuellement pratiquement aucun facteur empêchant la divulgation de la mémoire de pool dans Windows, et presque toutes ces erreurs peuvent être utilisées pour divulguer des données sensibles à partir de différentes parties du noyau.
Linux
Le noyau Linux possède trois interfaces principales pour l'allocation dynamique de mémoire:
- kmalloc - une fonction commune utilisée pour allouer des blocs de mémoire de taille arbitraire (continue dans l'espace d'adressage virtuel et physique), utilise l' allocation de mémoire de dalle .
- kmem_cache_create et kmem_cache_alloc - un mécanisme spécialisé pour allouer des objets de taille fixe (structures, par exemple), utilise également l' allocation de mémoire de dalle .
- vmalloc est une fonction d'allocation rarement utilisée qui renvoie des régions dont la continuité n'est pas garantie au niveau de la mémoire physique.
Ces fonctions (par elles-mêmes) ne garantissent pas que les régions sélectionnées ne contiendront pas d'anciennes données (potentiellement confidentielles), ce qui permet d'ouvrir la mémoire du tas du noyau. Cependant, le code appelant peut demander la mémoire annulée de plusieurs manières:
- La fonction kmalloc a un analogue de kzalloc , ce qui garantit que la mémoire retournée est effacée.
- L'indicateur __GFP_ZERO facultatif peut être transmis à kmalloc , kmem_cache_alloc et à d'autres fonctions pour obtenir le même résultat.
- kmem_cache_create accepte un pointeur sur une fonction constructeur optionnelle qui est appelée pour pré-initialiser chaque objet avant de le renvoyer au code appelant. Le constructeur peut être implémenté comme un wrapper autour d'un memset pour mettre à zéro une zone de mémoire donnée.
Nous considérons la disponibilité de ces options comme des conditions favorables à la sécurité du noyau, car elles encouragent les développeurs à prendre des décisions éclairées et leur permettent de simplement travailler avec les fonctions d'allocation de mémoire existantes au lieu d'ajouter des appels de memset supplémentaires après chaque allocation de mémoire dynamique.
Tableaux de taille fixe
L'accès à un certain nombre de ressources OS peut être obtenu par leurs noms de test. La variété des ressources nommées dans Windows est très grande, par exemple: fichiers et répertoires, clés et valeurs des clés de registre, fenêtres, polices et bien plus encore. Pour certains d'entre eux, la longueur du nom est limitée et est exprimée par une constante, telle que MAX_PATH (260) ou LF_FACESIZE (32). Dans de tels cas, les développeurs du noyau simplifient souvent le code en déclarant les tampons de taille maximale et en les copiant dans leur ensemble (par exemple, en utilisant le mot-clé sizeof) au lieu de travailler uniquement avec la partie correspondante de la ligne. Cela est particulièrement utile si les chaînes sont membres de structures plus grandes. Ces objets peuvent être déplacés librement dans la mémoire sans se soucier de la gestion des pointeurs vers la mémoire dynamique.
Comme vous vous en doutez, les grands tampons sont rarement utilisés complètement et l'espace de stockage restant n'est souvent pas vidé. Cela peut entraîner des fuites particulièrement graves de longues zones contiguës de mémoire du noyau. Dans l'exemple du Listing 5, l'appel système utilise la fonction RtlGetSystemPath pour charger le chemin système dans le tampon local, et si l'appel réussit, les 260 octets sont transmis à l'appelant, quelle que soit la longueur de ligne réelle.
NTSTATUS NTAPI NtGetSystemPath(PCHAR OutputPath) { CHAR SystemPath[MAX_PATH]; NTSTATUS Status; Status = RtlGetSystemPath(SystemPath, sizeof(SystemPath)); if (NT_SUCCESS(Status)) { RtlCopyMemory(OutputPath, SystemPath, sizeof(SystemPath)); } return Status; }
Listing 5: étendre la mémoire en initialisant partiellement le tampon de chaîne.
La région de mémoire copiée dans l'espace utilisateur dans cet exemple est illustrée à la figure 4.

Figure 4: Mémoire d'un tampon de ligne partiellement initialisé.
Une implémentation sécurisée ne doit renvoyer que le chemin demandé et non la totalité du tampon utilisé pour le stockage. Cet exemple montre une fois de plus comment l'estimation de la taille des données avec l'opérateur sizeof (utilisé comme paramètre pour RtlCopyMemory) peut être complètement incorrecte en ce qui concerne la quantité réelle de données que le noyau doit transmettre à la zone utilisateur.
Taille de sortie d'appel système arbitraire
La plupart des appels système acceptent des pointeurs vers la sortie en mode utilisateur ainsi que la taille du tampon. Dans la plupart des cas, les informations de taille ne doivent être utilisées que pour déterminer si le tampon fourni est suffisant pour recevoir la sortie d'appel système. N'utilisez pas toute la taille du tampon de sortie fourni pour spécifier la quantité de mémoire à copier. Cependant, nous voyons des cas où le noyau essaiera d'utiliser chaque octet du tampon de sortie de l'utilisateur, sans compter la quantité de données réelles qui doivent être copiées. Un exemple de ce comportement est illustré dans le Listing 6.
NTSTATUS NTAPI NtMagicValues(LPDWORD OutputPointer, DWORD OutputLength) { if (OutputLength < 3 * sizeof(DWORD)) { return STATUS_BUFFER_TOO_SMALL; } LPDWORD KernelBuffer = Allocate(OutputLength); KernelBuffer[0] = 0xdeadbeef; KernelBuffer[1] = 0xbadc0ffe; KernelBuffer[2] = 0xcafed00d; RtlCopyMemory(OutputPointer, KernelBuffer, OutputLength); Free(KernelBuffer); return STATUS_SUCCESS; }
Listing 6: étendre la mémoire via un tampon de sortie de taille arbitraire.
Le but d'un appel système est de fournir au code appelant trois valeurs spéciales de 32 bits, occupant un total de 12 octets. Bien que la vérification de la taille de tampon correcte au tout début de la fonction soit correcte, l'utilisation de l'argument OutputLength doit s'arrêter là. Sachant que le tampon de sortie est suffisamment grand pour enregistrer le résultat, le noyau peut allouer 12 octets de mémoire, le remplir et copier le contenu dans le tampon en mode utilisateur fourni. Au lieu de cela, un appel système alloue un bloc de pool (en outre, avec une longueur contrôlée par l'utilisateur) et copie toute la mémoire allouée dans l'espace utilisateur. Il s'avère que tous les octets, à l'exception des 12 premiers, ne sont pas initialisés et sont ouverts par erreur à l'utilisateur, comme le montre la figure 5.

Figure 5: Mémoire tampon de taille arbitraire.
Le schéma décrit dans cette section est particulièrement courant pour Windows. Une erreur similaire peut fournir à un attaquant une primitive extrêmement utile pour l'extension de la mémoire:
- , Windows, . , .
- . , , . , ( — ) .
, . , , .
,
, . , Windows .
, , . , : AddressSanitizer , PageHeap Special Pool . , , - . , . , , , , , . , ( ).
, , , . , .
, API
API, Windows (Win32/User32 API). API , , , . , , , , . .
, . , . , , , . , , .
, , . , KASLR (Kernel Address Space Layout Randomization ), . : Windows, Hacking Team 2015 ( Juan Vazquez. Revisiting an Info Leak ) (derandomize) win32k.sys, . , Matt Tait' Google Project Zero ( Kernel-mode ASLR leak via uninitialized memory returned to usermode by NtGdiGetTextMetrics ) MS15-080 (CVE-2015-2433).
(/) , , (control flow), : , , , , StackGuard Linux /GS Windows . , . , , .
(/)
(/) , , , : , , , . , , . . , ( , ) , , .

Microsoft Windows
2015 Windows. 2015 Matt Tait win32k!NtGdiGetTextMetrics. Windows Hacking Team. , , , 0-day Windows.
2015, WanderingGlitch (HP Zero Day Initiative) ( Acknowledgments – 2015 ). Ruxcon 2016 ( ) "Leaking Windows Kernel Pointers" .
, 2017 fanxiaocao pjf IceSword Lab (Qihoo 360) "Automatically Discovering Windows Kernel Information Leak Vulnerabilities" , , 14 2017 (8 ). Bochspwn Reloaded, , . VMware (Bochs) . , Bochspwn Reloaded, .
, , 2010-2011 , win32k: "Challenge: On 32bit Windows7, explain where the upper 16bits of eax come from after a call to NtUserRegisterClassExWOW()" "Subtle information disclosure in WIN32K.SYS syscall return values" . Windows 8, 2015 Matt Tait , : Google Project Zero Bug Tracker .
( ), , 2017 - Windows -, : Joseph Bialek — "Anyone notice my change to the Windows IO Manager to generically kill a class of info disclosure? BufferedIO output buffer is always zero'd" . , IOCTL- .
, Visual Studio 15.5 POD- , "= {0}", . , padding- () .
Linux
Windows, Linux , 2010 . , ( ) ( ) . , Windows Linux , — , .
, Linux . "Linux kernel vulnerabilities: State-of-the-art defenses and open problems" 2010 2011 28 . 2017- "Securing software systems by preventing information leaks" Lu K. 59 , 2013- 2016-. . : Rosenberg Oberheide 25 , Linux 2009-2010 , . Linux c grsecurity / PaX-hardened . Vasiliy Kulikov 25 2010-2011 , Coccinelle . , Mathias Krause 21 2013 50 .
, , Linux. — -Wuninitialized ( gcc, LLVM), . kmemcheck , Valgrind' . , . , KernelAddressSANitizer KernelMemorySANitizer . KMSAN syzkaller ( ) 19 , .
Linux. 2014 — 2016 Peir´o Coccinelle , Linux 3.12: "Detecting stack based kernel information leaks" International Joint Conference SOCO14-CISIS14-ICEUTE14, pages 321–331 (Springer, 2014) "An analysis on the impact and detection of kernel stack infoleaks" Logic Journal of the IGPL. , . 2016- Lu UniSan — , , : , . , 20% (350 1800), 19 Linux Android.
— (multi-variant program execution), , . , . , KASLR, -, . , 2006 DieHard: probabilistic memory safety for unsafe languages, 2017 — BUDDY: Securing software systems by preventing information leaks. John North "Identifying Memory Address Disclosures" 2015- . , SafeInit (Comprehensive and Practical Mitigation of Uninitialized Read Vulnerabilities) , , . , , , Linux.
, . , : , . , , - , . .
CONFIG_PAGE_POISONING CONFIG_DEBUG_SLAB, -. -, . , , , Linux.
grsecurity / PaX . , PAX_MEMORY_SANITIZE , slab , ( — ). , PAX_MEMORY_STRUCTLEAK , ( ), . padding- (), 100% . , — PAX_MEMORY_STACKLEAK, . , , . (Kernel Self Protection Project) STACKLEAK .
Linux:
Secure deallocation, Chow , 2005Chow, Jim and Pfaff, Ben and Garfinkel, Tal and Rosenblum, Mendel. Shredding Your Garbage: Reducing Data Lifetime Through Secure Deallocation. In USENIX Security Symposium, pages 22–22, 2005.
, , ( ) . Linux .
Split Kernel, Kurmus Zippel, 2014Kurmus, Anil and Zippel, Robby. A tale of two kernels: Towards ending kernel hardening wars with split kernel. In Proceedings of the 2014 ACM SIGSAC Conference on Computer and Communications Security, pages 1366–1377. ACM, 2014.
, .
SafeInit, Milburn , 2017Milburn, Alyssa and Bos, Herbert and Giuffrida, Cristiano. SafeInit: Comprehensive and Practical Mitigation of Uninitialized Read Vulnerabilities. In Proceedings of the 2017 Annual Network and Distributed System Security Symposium (NDSS)(San Diego, CA), 2017.
, , .
UniSan, Lu , 2016Lu, Kangjie and Song, Chengyu and Kim, Taesoo and Lee, Wenke. UniSan: Proactive kernel memory initialization to eliminate data leakages. In Proceedings of the 2016 ACM SIGSAC Conference on Computer and Communications Security, pages 920–932. ACM, 2016.
SafeInit , , , , .
, Linux .
( )
, , ( ). : (), , , , ( - ) . , . , , .
, :
- Bochspwn Reloaded – detection with software x86 emulation
- Windows bug reproduction techniques
- Alternative detection methods
- Other data sinks
- Future work
- Other system instrumentation schemes
, :) , .