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 !

ios

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.

ios

ios

ios

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.

ios

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.

Benoit

Network engineer at CNS Communications. CCIE #47705, focused on R&S, Data Center, SD-WAN & Automation.

More Posts - Website

Follow Me:
TwitterLinkedIn

Comments are Disabled