2025-01-21
Parmi les propositions mal-aimées de la norme C23, j'invoque memset_explicit().
Bon d'accord, c'est pas si nouveau : pour C11 on avait déjà memset_s(), dont ce n'est que l'évolution à un paramètre près.
Dans les deux cas, le support s'en est trouvé relégué à l'annexe K, c'est-à-dire le morceau de la norme qu'on est "invité à, mais pas obligé" d'implémenter. Concrètement et ironiquement, seuls deux célèbres compilateurs propriétaires l'implémentent ici et là ; on y revient…
La raison fondamentale est qu'on touche ici à une zone grise : le rôle d'un développeur est d'exprimer une intention, le compilateur d'interpréter et éventuellement optimiser. Mais que se passe-t-il quand les deux se mêlent, voire se brouillent ?
Prenons ce code par exemple:
#define PW_LEN 16
char password[PW_LEN+1];
scanf("%PW_LENs", password);
[...]
memset(password, 0, PW_LEN);
return;
On saisit un mot de passe qu'on chiffre éventuellement, puis on l'utilise (non montré), enfin on le nettoie avec memset() pour qu'il n'en reste pas trace dans la mémoire de la machine hôte. Et après on n'y accède plus jamais. Logique.
Que se dit le compilateur, avec un niveau d'optimisation de type "release" (= dés "-O1" pour GCC) ? "Pas la peine de garder la dernière instruction, elle ne sert à rien et on y gagne !"
Et c'est comme ça qu'on passe par exemple de cet assembleur x86 :
pxor %xmm0, %xmm0
movups %xmm0, (%rsp)
ou alors :
rep stos %al, (%edi)
à… rien du tout.
Si si, ça se reproduit facilement avec cet exemple ; pas une trace. Autrement dit, paie ta "mesure de sécurité" !
Le problème est ici que l'intention du développeur est déjouée par le compilateur faute de contexte. Et comme le contexte en C c'est inexistant, on espère qu'il soit véhiculé par une fonction… explicite, dont l'implémentation concrète sera malheureusement très variable car hyper-dépendante de la plate-forme matérielle ET logicielle.
Bon après, concrètement en son absence ? Alors presque chacun a sa solution maison qui marche bien tant qu'on reste… à la maison justement.
Entre autres :
Face à ce chaos ambiant, je présente :
(qui s'appuie en partie sur le bon travail préalable d'un monsieur)
Elle n'est forcément pas parfaite, mais je garantis que je l'ai essayée partout où j'ai raisonnablement pu.
Elle fait appel à l'annexe K dans les rares cas où c'est dispo, à défaut tente la solution maison, se replie sur un code assembleur x86/ARM si ça convient, et en dernier recours tente de forcer la main du compilateur.
On peut essayer les étapes du README, et me dire si tout fonctionne dans son propre cas particulier...
... j'en serai très reconnaissant 😃 !