web-dev-qa-db-fra.com

Comment comparer la sécurité d'un programme compilé avec drapeau d'optimisation?

J'aimerais savoir comment je peux savoir quelle est la ligne de compilation la plus sûre, c'est-à-dire:

Avoir plusieurs lignes de compilation, par exemple, GCC, comment savoir lequel est le plus sécurisé? Le durcissement serait une bonne solution? Que recommandez-vous?

Les drapeaux d'optimisation des compilations affectent-ils la sécurité?

2
sgio

Les options du compilateur peuvent influencer la sécurité du programme résultant de plusieurs manières différentes. De manière générale, l'optimisation peut nuire à la sécurité. Cependant, ce n'est pas une raison pour désactiver toujours les optimisations! Par exemple, si votre code de cryptage est si lent que les utilisateurs désactivent la fonctionnalité de cryptage et envoient des données sur ClearText, c'est pire la sécurité.

Comportement non défini

Les langages de bas niveau tels que C et C++ ont beaucoup de domaines avec comportement non défini . "Comportement non défini" signifie que tout peut arriver et les compilateurs en tirent parti pour optimiser le code.

Il y a une forte déconnexion entre la façon dont les développeurs typiques voient le code et la manière dont les compilateurs voient le code. Souvent, ce qui ressemble à une optimisation évidente d'un développeur est difficile pour un compilateur de détection, tandis que les compilateurs peuvent effectuer des optimisations qui semblent le développeur comme il exploite une échappatoire.

Un exemple classique est que l'optimisation des compilateurs éliminera les contrôles redondants. Cela semble bien, non?

if (config == NULL) return ERROR_BAD_CONFIGURATION;
… // code that doesn't modify config
if (config != NULL) x = config->value;

Un compilateur d'optimisation est susceptible de compiler cette dernière ligne comme si elle avait lu x = config->value Depuis qu'il sait que config ne peut pas être nul à ce stade. Considérez maintenant ceci:

int *p = &config->value;
if (config == NULL) return ERROR_BAD_CONFIGURATION;
…
x = *p;

config->value promet que config est non nul à ce stade. Par conséquent, le chèque config == NULL ci-dessous est redondant et un compilateur d'optimisation est susceptible de le supprimer. Donc, si config est NULL au moment de l'exécution, x est défini sur une valeur lue d'une adresse non valide, entraînant un crash ou un pire. Ceci est un exemple dans lequel un compilateur d'optimisation fait une mauvaise insécurité du code, tandis qu'un compilateur anti-optimisation permettrait au programme d'être incorrect.

Désactiver l'optimisation entraîne généralement moins d'effets surprenants, mais ce n'est pas une garantie. La solution pour de tels "bugs induits par l'optimisation" est d'écrire un code correct en premier lieu, plutôt que d'espérer que le compilateur déterminera l'intention du programmeur.

Le comportement non défini ne se limite pas aux langues de bas niveau. En particulier, quelle que soit la langue, Concurrence a de manière intrinsèquement un comportement indéfini, car l'ordre dans lequel les choses se produisent n'est pas déterminée. conditions de course peut arriver dans n'importe quelle langue (même sans support intégré à la concurrence, cela peut se produire via la communication entre programmes). Les options du compilateur ont généralement peu d'influence là-bas.

Canaux latéraux

Certaines vulnérabilités exploitent les moyens directs d'accéder à des informations, par exemple un chèque d'autorisation manquant ou un tampon de tampon. D'autres vulnérabilités sont dus à des moyens indirects d'accéder aux informations: canaux latéraux . Un canal latéral commun est chronométrage , y compris le calendrier des événements indirects.

Par exemple, sur un système haut de gamme typique tel qu'un ordinateur personnel ou un smartphone, le système d'exploitation protège les programmes d'accéder à la mémoire de chacun. Cependant, un programme peut toujours être en mesure de déduire des informations de la manière dont les autres programmes se comportent. En particulier, en mesurant le calendrier des accès à la mémoire, un programme d'attaquant peut déduire quels cache lignes Un programme de victime est accessible, ce qui fournit des informations sur les accès aux accès des victimes en mémoire. Par exemple, le moyen facile et rapide de mettre en œuvre l'algorithme cryptographique populaire [~ # ~ # ~ # ~ ~] utilise des tables de levage; Si l'attaquant sait quelle ligne de la table accède à la victime, cela peut permettre à l'attaquant de reconstruire la clé.

Les programmes peuvent être écrits de manière défensive pour éviter de fuir des informations de cette manière. Cela est parfois difficile lors de l'optimisation des compilateurs du code sans fuites évidentes dans un code exécutable qui a des fuites, car le compilateur a déterminé que le programme serait plus rapide de cette façon. Dans ce cas, un compilateur d'optimisation peut rendre un code même non sécurisé de code.

Bugs compilateur

Les bugs du compilateur sont rares, moins communs que des insectes dans une application aléatoire. Tout développeur décent sait que si le programme ne fonctionne pas, un bogue dans leur propre code est beaucoup, beaucoup plus probable qu'un bug de compilateur. Néanmoins, les bugs du compilateur peuvent se produire. Plus le niveau d'optimisation est élevé, plus le risque de bugs est élevé. La plupart des codes ne bénéficient pas des niveaux d'optimisation les plus élevés de toute façon: généralement -O ou -O2 est un bon compromis entre performance et risque.

Des dispositifs de sécurité

Certaines fonctionnalités de sécurité sont en partie sous la commande du compilateur. Là, la chose évidente à rechercher est de savoir si ces fonctionnalités de sécurité sont activées. Ces caractéristiques de sécurité ne contribuent généralement pas à se défendre contre les vulnérabilités, mais elles peuvent faire des exploits plus difficiles. Ils ne font souvent pas des exploits complètement impossibles, mais ils peuvent faire la différence entre prendre 5 minutes et 5 semaines pour créer un exploit, ce qui peut donner au développeur le temps de développer et de déployer un patch.

Par exemple, de nombreux compilateurs peuvent générer Stack Canaries . Les canaris de pile détectent lorsqu'un débordement tampon se produit sur la pile et arrête le programme. Pour exploiter un débordement de tampon de pile, l'attaquant doit limiter la taille du débordement de manière à ce que le canal ne soit pas écrasé ni arrangé pour que la valeur correcte soit écrite dans la place du Canary, dont aucun n'est toujours possible ou facile.