Analyse de _FORTIFY_SOURCE dans le contexte des protections / durcissements de binaires.

Qu’est-ce que _FORTIFY_SOURCE

  • _FORTIFY_SOURCE est une macro / mécanisme de durcissement qui agit au niveau de la compilation + runtime pour fournir une « protection légère » contre certains débordements mémoire (buffer-overflow) et vulnérabilités liées aux chaînes / fonctions de manipulation mémoire1.

  • Concrètement, quand _FORTIFY_SOURCE est activé (par exemple via -D_FORTIFY_SOURCE=2 ou, plus récemment, -D_FORTIFY_SOURCE=3), le compilateur (GCC / clang) remplace certaines fonctions « dangereuses » (comme memcpy, strcpy, sprintf) par des versions « fortifiées » — des wrappers (souvent appelés __xxx_chk) qui, avant d’effectuer la copie ou l’écriture, vérifient que la taille de la destination est suffisante, et abortent (crash) le programme si ce n’est pas le cas2.

  • Le niveau de protection dépend de la version :

    • _FORTIFY_SOURCE=1 : quelques vérifications, sans changer le comportement des programmes conformes1 ;
    • _FORTIFY_SOURCE=2 : plus de fonctions « fortifiées »1 ;
    • depuis glibc 2.34 / GCC 12, un niveau _FORTIFY_SOURCE=3 existe pour améliorer la couverture — il essaie d’évaluer plus précisément la taille des objets passés aux fonctions, ce qui rend les vérifications plus robustes3.
  • L’impact en runtime / performance est jugé faible : l’ajout de ces vérifications n’engendre pas — en pratique — un surcoût systématique significatif4.

Ce que ça protège (et ce que ça empêche)

Quand _FORTIFY_SOURCE est actif :

  • Les appels à des fonctions string/memory potentiellement dangereux sont « interceptés ». Avant d’effectuer l’opération, la version _chk vérifie que la destination a assez de place — ce qui empêche un overflow visible dans des cas simples (par exemple strcpy(dest, src)src dépasse la taille de dest)1.
  • Si l’appel viole la condition (overflow possible), le programme est immédiatement interrompu — généralement avec un message type *** buffer overflow detected *** et un core-dump5.
  • Cela couvre un certain nombre de fonctions « à risque » : memcpy, memmove, strcpy, strcat, sprintf1.

Donc _FORTIFY_SOURCE fournit une mitigation « préventive » contre des débordements basiques liés aux buffers — un peu comme un « filet de sécurité » (fail-safe) pour des fonctions classiques de manipulation mémoire / chaînes.

Les limites de _FORTIFY_SOURCE — ce qu’il ne peut pas empêcher

Important : _FORTIFY_SOURCE n’est pas une protection universelle. Voici ses limites / cas où il ne sert à rien :

  • Portée limitée des fonctions : seul un certain nombre de fonctions standard (memcpy, strcpy) sont « fortifiées ». Tout autre code (manipulation manuelle, arithmétique de pointeurs, usage de read(), recv(), gets() non remplacée) n’est pas protégé par _FORTIFY_SOURCE6.
  • Obligation d’optimisation / compilation « correcte » : les vérifications fonctionnent quand le code est compilé avec un certain niveau d’optimisation (souvent -O1 ou plus) et les bons flags (_FORTIFY_SOURCE), sinon les checks peuvent ne pas être insérés1.
  • Complexité des cas dynamiques : si les tampons sont alloués dynamiquement, via des allocateurs personnalisés, ou manipulés d’une façon non triviale, le compilateur peut ne pas être capable de déterminer la « bonne » taille à vérifier → la protection échoue4.
  • Ne protège ni contre les corruptions de heap/stack complexes : un overflow de pile, un débordement sur heap, corruptions via use-after-free, corruption de structures, ne sont pas couverts — sauf si le bug passe par une des fonctions « fortifiées »7. _FORTIFY_SOURCE c’est un filet, mais loin d’être exhaustif.
  • Ne remplace pas d’autres protections : c’est une brique parmi d’autres. D’autres mitigations comme RELRO, NX, ASLR, canaris de pile restent nécessaires pour un hardening complet.

En tant qu’ingénieur sécurité / reverse-engineer : ce que ça signifie

Vu la préparation à des exercices de reverse / exploitation, _FORTIFY_SOURCE a des implications intéressantes (et des pièges) :

  • Si l’on tombe sur un binaire compilé avec _FORTIFY_SOURCE, des « overflows simples » via strcpy, memcpy peuvent être plus difficiles — le programme crash plutôt que de permettre l’écrasement silencieux. Cela pourrait rendre certaines primitives d’exploitation (stack overflow + ret2…) plus difficiles.
  • Mais ce n’est qu’une protection « fragile » : si le binaire utilise des fonctions custom, des lectures directes (read), des manipulations de mémoire mal encadrées, ou des allocateurs maison → _FORTIFY_SOURCE ne nous empêchera probablement pas d’exploiter la vulnérabilité.
  • Donc : ne jamais miser sur _FORTIFY_SOURCE comme seul bouclier — c’est un plus, mais il faut toujours vérifier l’absence d’autres vecteurs d’attaque (heap, stack, use-after-free, corruption de structures).

_FORTIFY_SOURCE vs RELRO / autres hardenings — ce qu’ils apportent de différent

ProtectionObjectif principalCe qu’elle protègeCe qu’elle ne protège pas
_FORTIFY_SOURCEVérifications / sécurisation des fonctions memory/stringBuffer-overflows simples liés à strcpy/memcpyOverflows manuels, heap/stack/alloc dynamiques, use-after-free, corruption mémoire complexe
RELRO (full)Rendre la GOT/PLT en lecture-seule après relocationEmpêcher les GOT-overwrite (redirection de fonctions)Overflows de pile, heap, format-string, corruption autres sections
NX, ASLR, canari, PIEDiverses : empêcher exécution de shellcode, randomiser adresses, détecter corruption de pileLarge panel (injection, ROP, ret2libc, corruption retour)Ne protègent pas les bugs logiques, les fuites d’information

Ces protections sont complémentaires — _FORTIFY_SOURCE est une protection au niveau du source + runtime, tandis que RELRO, NX, etc. agissent au niveau binaire / mémoire / runtime.

Conclusion

  • _FORTIFY_SOURCE est un « gadget » intéressant pour durcir des programmes C/C++, en particulier contre des débordements mémoire simples.
  • Ce n’est pas une panacée : beaucoup de cas d’exploitation restent possibles, surtout dans un contexte de reverse / pwn / exploitation — il ne remplace pas les protections « à bas niveau » comme RELRO, NX, ASLR.
  • Dans un contexte d’étude / exploitation, c’est surtout un faux ami potentiel : il peut donner l’impression qu’un binaire est « durci / safe », mais dès qu’on descend dans le détail (heap, allocations dynamiques, logique, overflows manuels), il faut rester vigilant.

Footnotes

  1. Red Hat — Enhance application security with FORTIFY_SOURCE. 2 3 4 5 6

  2. Red Hat — Security Technologies: FORTIFY_SOURCE.

  3. GCC’s new fortification level: the gains and costs.

  4. How to improve application security using FORTIFY_SOURCE=3. 2

  5. Enhance application security with FORTIFY_SOURCE — Red Hat security.

  6. A developer’s guide to secure coding with FORTIFY_SOURCE.

  7. Linux binary security hardening.