IOS et Gestion de la mémoire
Aujourd’hui, j’ai eu besoin de m’intéresser au fonctionnement de la mémoire de l’IOS, voici donc un petit article à ce sujet grâce aux informations que j’ai pu trouver ici et là, cela pourrait vous intéresser. Surtout les commandes IOS pour vérifier l’état de la mémoire de votre routeur.
Comme tout OS, l’IOS doit implémenter un certain nombre de principes élémentaires :
- Gestion de processus
- Gestion de la mémoire
- Gestion des périphériques
- …
Nous allons nous intéresser plus particulièrement au système de gestion de la mémoire. Les OS récents travaillent avec de la mémoire protégée. Un processus x ne peut pas accéder à la mémoire d’un processus y. Pour que le processus x dialogue avec le processus y, ils vont devoir employer d’autres méthodes (Shared Memory, Message Queues, Pipes, Network Connections, …). Ces méthodes sécurisent les processus entre-eux mais ralentissent néanmoins leurs fonctionnements. L’IOS ne gère pas la mémoire partagée, tous les processus ont accès à la mémoire sans restrictions. Un processus est donc libre de dialoguer avec un autre en écrivant dans la mémoire de ce dernier (Buffer Overflow = Crash). Il existe néanmoins une notion de mémoire R/O et R/W.
L’IOS travaille avec des pools de mémoire, c’est le Pool Manager qui en est responsable.
Ici, un pool réservé au processor et un pool réservé aux I/O:
Router#show memory Head Total(b) Used(b) Free(b) Lowest(b) Largest(b) Processor 653B8C20 155481056 86243592 69237464 68168948 67670028 I/O EE800000 25165824 5269012 19896812 19819968 19871932
- Head : début du pool
- Total : taille du pool en bytes
- Used : utilisation actuelle du pool en bytes
- Free : mémoire libre actuelle du pool en bytes
- Lowest : mémoire libre historiquement la plus basse en bytes
- Largest : le plus grand bloc de mémoire libre contigüe
Ces mêmes pools appartiennent à des régions de mémoires gérées par le Region Manager :
Router#show region Region Manager: Start End Size(b) Class Media Name 0x0E800000 0x0FFFFFFF 25165824 Iomem R/W iomem:(iomem) 0x60000000 0x6E7FFFFF 243269632 Local R/W main 0x6000F000 0x632FFFFF 53415936 IText R/O main:text 0x63300000 0x64DFFCFF 28310784 IData R/W main:data 0x64DFFD00 0x653B8C1F 6000416 IBss R/W main:bss 0x653B8C20 0x6E7FFFFF 155481056 Local R/W main:heap 0x80000000 0x8E7FFFFF 243269632 Local R/W main:(main_k0) 0xA0000000 0xAE7FFFFF 243269632 Local R/W main:(main_k1) 0xEE800000 0xEFFFFFFF 25165824 Iomem R/W iomem
Le pool de mémoire Processor est donc compris dans la région main:heap. Cette région fait partie de la région main qui commence en 0×60000000 et finit en 0×6E7FFFFF. Le pool de mémoire I/O représente la région iomem de 0xEE800000 à 0xEFFFFFFF.
Dans la région main, on trouve :
- main:text : contient le code de l’IOS en R/O (IText)
- main:data : contient les variables initialisées R/W (IData)
- main:bss : contient les variables non initialisées R/W (IBss)
- main:heap : contient les structures de mémoire standard locales R/W
- iomem : contient la mémoire des périphériques (mémoire pour le bus I/O)
On peut remarquer que certaines régions sont redondantes: main:(main_k0) ; main:(main_k1) ; Elles correspondent toutes à la même région main. On peut aussi retrouver iomem à deux endroits différents : 0×0E800000->0×0FFFFFFF et 0xEE800000->0xEFFFFFFF, il s’agit pourtant de la même zone mémoire.
D’un périphérique CISCO à un autre, l’endroit où est stocké tel ou tel type de mémoire diffère. Sur un type de routeur on peut trouver de la mémoire SRAM pour iomem, tandis que dans d’autres pour la même zone on trouvera de la mémoire DRAM. Le Pool Manager définit des zones mémoires indépendamment du type de mémoire utilisé (hardware abstraction).
Revenons au Pool Manager. En utilisant la commande « show memory processor », on peut remarquer que la mémoire est subdivisée en blocs :
Router#show memory processor Processor memory Address Bytes Prev Next Ref PrevF NextF Alloc PC what 65A817E0 0000000084 65A8175C 65A81864 001 -------- -------- 628215E8 Init 65A81864 0000001372 65A817E0 65A81DF0 001 -------- -------- 608E3218 Skinny Socket Server 65A81DF0 0000001156 65A81864 65A822A4 001 -------- -------- 608E3218 Skinny Socket Server
- Address : début du bloc
- Bytes : taille du bloc
- Prev : adresse du bloc précédent (chaînage)
- Next : adresse du bloc suivant (chaînage)
- Ref : par combien de processus ce bloc est-il utilisé ?
- PrevF : bloc libre précédent
- NextF : bloc libre suivant
- Alloc PC : processus ayant alloué ce bloc
- What : nom du processus détenant le bloc
S’il nous venait à l’idée de réaliser un buffer overflow dans une zone mémoire, donc écrire plus de bytes prévu que ceux alloués pour cette zone mémoire, on finirait par écraser le header de la prochaine zone mémoire et donc rompre le chaînage mémoire IOS.
Hélas, ou pas, l’IOS vérifie en permanence la structure de sa mémoire et à la moindre incohérence, force un crash.
Donc pour réussir un buffer overflow sans crash, il faut s’arranger pour réécrire un header cohérant sur la prochaine zone mémoire.
Ne pas oublier non plus de faire un « jmp » pour sauter à la suite de son code juste après le header !
Chunk manager
Lorsque l’on alloue de la mémoire à un processus (malloc), Le Pool Manager prend une zone de mémoire libre et l’attribue à un processus. Le Pool Manager tient donc une table de blocs de mémoire contigus. Lorsqu’un processus libère une zone mémoire (free), le Pool Manager essaie de concaténer la zone de mémoire fraichement libérée avec ses voisines. Malgré cette concaténation une fragmentation est inévitable.
Une mémoire extrêmement fragmentée peut mener à des erreurs de malloc « %SYS-2-MALLOCFAIL ».
En effet, il se peut qu’il y ait suffisamment de mémoire disponible, mais pas de blocs contigus suffisant pour permettre la taille de malloc demandée.
Dans ce dernier exemple, il n’y a que des petits blocs non contigus libérés, résultat : Si un processus désire allouer une zone mémoire plus importante, il ne pourra pas le faire et nous aurons un « MALLOCFAIL ». Le Chunk Manager va permettre de régler ces soucis en allouant plus intelligemment la mémoire aux processus.
Le Chunk Manager est responsable de l’allocation de Chunk. Un Chunk contient un nombre de blocs finis de taille égale. Si on utilise tous les blocs présents dans un Chunk, le Chunk Manager alloue un nouvel espace (Sibling). Si plus aucun des blocs de ce « Sibling » n’est utilisé, il est libéré « Freed/Trimmed ».
Un processus se voit donc attribuer une plus grande zone mémoire découpée en plus petits blocs.
Lorsque le processus libère un bloc, il le fait dans son Chunk. Il n’y a donc plus de fragmentation entre différents processus.
–
–
–
–
–
–
–
–
–
–
Router#show chunk Chunk Manager: 407660 chunks created, 406281 chunks destroyed 9349 siblings created, 406279 siblings trimmed Chunk element Block Maximum Element Element Total Cfgsize Ohead size element inuse freed Ohead Name 16 4 940 33 2 31 360 String-DB owne 0x654C2408 16 4 940 33 0 33 360 String-DB cont 0x654C27B4 312 16 65588 197 38 159 4072 Extended ACL e 0x654C3484 96 16 20052 171 9 162 3584 ACL Header 0x654D34B8 8536 0 65588 7 1 6 5784 Parseinfo Bloc 0x654DA14C 16 0 456 15 1 14 164 tokenQ node 0x654EA180 20 0 456 13 13 0 144 Chain Cache No 0x654EA348 20 0 456 13 6 7 144 (sibling) 0x66BD0E50 20 0 460 13 13 0 148 (sibling) 0x66F33EE0
Au sein d’un Chunk, les blocs possèdent un header (ou non taille=0). La taille du header par Chunk est fixe (0, 4, 16, 20, 24).
Ex : Extended ACL, un Chunk de maximum 197 éléments avec 312 éléments au départ. Chaque bloc possède un header de 16 Bytes et pèse 65.588 Bytes. J’ai actuellement 38 blocs utilisés dans ce Chunk.
Comments are Disabled