Post

Partie 19 - Les small bins - mécanismes de protection (2/2)

Les small bins : mécanismes de protection (2/2)

Protections selon les versions

  • 2.3.4 : Safe Unlink : mise en place d’une vérification de l’intégrité des liens lors du retrait d’un bloc libre (unlink). La glibc vérifie alors que les pointeurs du bloc ciblé (victim) sont cohérents avec ceux de son prédécesseur et de son successeur ;
  • 2.26 : Contrôle de la taille du bloc victim à retirer via prev_size lors de l’exécution de unlink.

Message d’erreur associé : corrupted double-linked list.

Nous avons déjà vu précédemment que les protections appliquées à la version 2.3.4 datent de … 2004 ! Oui, ça fait déjà pas mal de temps 😅.

Ne pas confondre le Safe Unlink avec le Safe Linking ! Ce sont des protections totalement différentes qui n’agissent pas de la même manière.

Cette protection n’est pas propre aux small bins car elle est également utilisée avec les large bins. La vérification est présente dans la fonction unlink dont le contenu, sous forme de macro, est le suivant :

1
2
3
4
5
6
7
8
9
10
11
/* Take a chunk off a bin list */
#define unlink(P, BK, FD) {                                            \
  FD = P->fd;                                                          \
  BK = P->bk;                                                          \
  if (__builtin_expect (FD->bk != P || BK->fd != P, 0))                \
    malloc_printerr (check_action, "corrupted double-linked list", P); \
  else {                                                               \
    FD->bk = BK;                                                       \
    BK->fd = FD;                                                       \
  }                                                                    \
}

P est le bloc libre à retirer de la corbeille. Il est aussi souvent appelé victim. Ne me demandez pas d’où ça vient, je n’en ai aucune idée 🙃.

La fonction unlink, nous en avons déjà brièvement parlé. Littéralement “défaire les liens”, cette fonction est appelée lorsqu’un bloc libre doit être retiré d’une corbeille. Sachant qu’en mémoire, les blocs libres dans une corbeille ne sont que des zones mémoire liées ou doublement liées, retirer un bloc revient à supprimer ces liens.

Avant de rentrer dans les détails du Safe Unlink, comprenons déjà comment fonctionne unlink lorsqu’un bloc victim est à retirer d’une corbeille. Cela peut arriver lorsque le bloc en question est adapté à une allocation demandée.

unlink n’est exécutée que pour deux types de corbeilles :

  • les small bins ;
  • les large bins.

Afin de vous éviter une migraine en essayant de comprendre ce vers quoi pointe une expression du genre FD->bk == P->fd->bk, voici un schéma qui met en exergue le fonctionnement de unlink :

Par souci de clarté, certains liens fd et bk qui ne sont pas nécessaires à la compréhension du schéma ont été omis.

Avec ce schéma, vous ne devriez plus avoir trop de mal à comprendre ce qui est vérifié dans la condition FD->bk != P || BK->fd != P 😉.

Version 2.26 - Contrôle de la taille du bloc victim à retirer

Message d’erreur associé : corrupted size vs. prev_size.

Une nouvelle vérification a été ajoutée dans la macro unlink lors de la version 2.26.

Dans les premiers temps, unlink était une simple petite macro mais est désormais une fonction à part entière et contient bien plus de vérifications. Voici à quoi elle ressemble dans la version 2.41 :

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
/* Take a chunk off a bin list.  */
static void
unlink_chunk (mstate av, mchunkptr p)
{
  if (chunksize (p) != prev_size (next_chunk (p)))
    malloc_printerr ("corrupted size vs. prev_size");

  mchunkptr fd = p->fd;
  mchunkptr bk = p->bk;

  if (__builtin_expect (fd->bk != p || bk->fd != p, 0))
    malloc_printerr ("corrupted double-linked list");

  fd->bk = bk;
  bk->fd = fd;
  if (!in_smallbin_range (chunksize_nomask (p)) && p->fd_nextsize != NULL)
    {
      if (p->fd_nextsize->bk_nextsize != p
	  || p->bk_nextsize->fd_nextsize != p)
	malloc_printerr ("corrupted double-linked list (not small)");

      if (fd->fd_nextsize == NULL)
	{
	  if (p->fd_nextsize == p)
	    fd->fd_nextsize = fd->bk_nextsize = fd;
	  else
	    {
	      fd->fd_nextsize = p->fd_nextsize;
	      fd->bk_nextsize = p->bk_nextsize;
	      p->fd_nextsize->bk_nextsize = fd;
	      p->bk_nextsize->fd_nextsize = fd;
	    }
	}
      else
	{
	  p->fd_nextsize->bk_nextsize = p->bk_nextsize;
	  p->bk_nextsize->fd_nextsize = p->fd_nextsize;
	}
    }
}

La vérification qui nous intéresse est dans la première condition if.

Vous remarquerez qu’une troisième vérification est présente dans la fonction unlink. Toutefois, cette vérification utilise les métadonnées fd_nextsize et bk_nextsize que nous n’avons pas encore vues et qui sont utilisées par les large bins 🔜.

Détails de la vérification

Je pense qu’avec le niveau de maîtrise du tas que vous avez désormais, il n’y a pas besoin d’épiloguer 😌 : la taille du bloc victim à retirer est comparée avec le champ prev_size du bloc situé immédiatement après victim (en se basant uniquement sur la taille du bloc victim).

This post is licensed under CC BY 4.0 by the author.