- La conversion de caractères Best-Fit de Windows remplace des caractères par d’autres qui leur ressemblent lors du passage d’une chaîne UTF-16 vers une page de code ANSI, et ce comportement devient une surface d’attaque WorstFit menant à des Path Traversal, des injections d’arguments et du RCE
- Le problème vient d’une combinaison entre les API ANSI, le runtime C/C++, le code de démarrage injecté par le compilateur et l’usage d’API de caractères non wide par les développeurs ; les chemins
GetCommandLineA,GetEnvironmentVariableA,getenvetint main()sont concernés - CVE-2024-4577 a contourné un correctif de PHP-CGI dans les pages de code chinoises/japonaises, car U+00AD y est converti en
-, tandis que le Filename Smuggling fait basculer¥,₩et la slash pleine chasse en/ou\, créant une confusion de chemin - Argument Splitting permet à des guillemets doubles pleine chasse ou à des signes yen/won de produire des caractères de parsing de ligne de commande, injectant des arguments dans des outils CLI comme
wget.exe,tar.exe,openssl.exeoujava.exe, et les échappements d’arguments classiques de PHP, Python, Node.js et Rust ne suffisent généralement pas à l’empêcher - Pour atténuer le problème, il faut activer l’option UTF-8 de Windows ou utiliser les Wide Character API ainsi que des chemins wide comme
_wgetcwd,_wgetenvetwmain(); tant que Microsoft n’activera pas UTF-8 par défaut sur toutes les éditions de Windows, des problèmes similaires pourront se répéter
Structure d’encodage de Windows et Best-Fit
- Windows utilisait à l’origine des pages de code ANSI, avec des pages de code différentes selon les langues, comme 1252, 932, 936, 949 ou 950
- L’ACP (ANSI Code Page) est utilisée pour la plupart des applications et réglages système, notamment pour les opérations sur les fichiers et les variables d’environnement
- L’OEMCP (Original Equipment Manufacturer Code Page) est surtout utilisée pour les communications avec les périphériques, comme la lecture/écriture dans la console
chcpaffiche l’OEMCP et non l’ACP ; ce n’est donc pas un moyen de vérifier l’ACP, qui est au cœur de cette recherche
- Windows est passé à Unicode au milieu des années 1990, et ses API principales utilisent aujourd’hui des wide characters basés sur UTF-16
- Les API principales pour le système de fichiers, les informations système et le traitement de texte ont basculé vers des API wide character
- La prise en charge d’UTF-8 existe, mais elle n’est pas activée par défaut dans la plupart des langages, et l’article la décrit comme étant au stade bêta
- Pour préserver la rétrocompatibilité, les API Windows proposent à la fois des versions ANSI et Unicode
- Les API ANSI portent le suffixe
A, commeGetEnvironmentVariableA - Les API Unicode portent le suffixe
W, commeGetEnvironmentVariableW - Lorsqu’une API ANSI est appelée, Windows convertit les chaînes internes UTF-16 en chaînes ANSI via
RtlUnicodeStringToAnsiStringouWideCharToMultiByte
- Les API ANSI portent le suffixe
Comment Best-Fit devient WorstFit
- Best-Fit est le mécanisme qui mappe un caractère UTF-16 vers un caractère visuellement ou conceptuellement proche quand il ne peut pas être représenté exactement dans la page de code ANSI cible
- Par exemple, en Windows-1252,
∞U+221E est mappé vers8 √π⁷≤∞peut devenir"vp7=8"après passage par une API ANSI
- Par exemple, en Windows-1252,
- Le mapping varie selon la page de code
¥U+00A5 est mappé vers\dans la page de code japonaise 932- Dans la page de code d’Europe centrale 1250, il est mappé vers
Y - Dans la plupart des autres pages de code, il reste inchangé
- Cette même conversion se produit non seulement lors d’appels directs aux API Windows, mais aussi dans les fonctions CRT et dans le chemin classique de
main- La conversion Best-Fit s’applique dans des fonctions CRT non wide comme
getenv - Elle intervient aussi quand les arguments et variables d’environnement sont reçus sous la forme
int main(int argc, char* argv[], char* envp[]) - Cela vient de la combinaison entre le code de démarrage CRT injecté par le compilateur et l’usage d’API Windows ANSI
- La conversion Best-Fit s’applique dans des fonctions CRT non wide comme
- Pour vérifier les mappings, on peut consulter Best-fit Mapping Grepper et les données brutes de mapping WindowsBestFit de Unicode.org
Premier cas WorstFit : PHP-CGI CVE-2024-4577
- CVE-2024-4577 est un cas d’attaque WorstFit qui permettait de compromettre un serveur PHP-CGI configuré avec des pages de code chinoises/japonaises via une simple requête
?%ADs- Les pages de code concernées sont 932 (japonais), 936 (chinois simplifié) et 950 (chinois traditionnel)
- Le caractère menaçant est
U+00AD
- La vulnérabilité PHP-CGI de 2012 était une injection d’arguments causée par le traitement automatique de la chaîne de requête par Apache comme premier argument du programme CGI
- En ajoutant
?-s, il était possible de divulguer le code source de la page et d’obtenir du RCE - Le correctif PHP consistait à arrêter le parsing des arguments si la chaîne de requête commençait par un tiret
- En ajoutant
- À cause de Best-Fit, le soft hyphen U+00AD est converti en
-dans les pages de code chinoises/japonaises, ce qui contourne le correctif existant?%ADspeut être interprété comme-sdu point de vue de PHP-CGI- C’est à partir de ce cas que l’équipe de recherche a découvert pour la première fois le terme Best-Fit
Filename Smuggling : quand les caractères de chemin sont convertis
- Filename Smuggling est une attaque dans laquelle des caractères Unicode présents dans un nom de fichier deviennent
/ou\dans un chemin passant par une API ANSI, ce qui peut provoquer une traversée de répertoires- Les API concernées incluent
GetCurrentDirectoryA,getcwd,FindFirstFileA,findfirst*etGetFullPathNameA - Les pages de code affectées sont 874, 125x, 932 (JP) et 949 (KR)
- Les caractères menaçants sont
/U+FF0F,\U+FF3C,¥U+00A5 (JP) et₩U+20A9 (KR)
- Les API concernées incluent
d8.exe, le Developer Shell de Chrome V8, utiliseGetCurrentDirectoryA()dans son implémentation interne pour obtenir le répertoire de travail courant- S’il est possible de créer un répertoire de travail contenant des caractères Unicode malveillants, l’accès via une API ANSI peut les transformer en charge utile de traversée de chemin
- L’exemple donné montre un accès non intentionnel à
C:/windows/win.ini
- L’implémentation Windows de
Dir.getwd()dans mruby dépend de la fonction CRT ANSI_getcwd()- La valeur renvoyée peut être altérée
- Cela peut déboucher sur une Path Traversal
Cuckoo Sandbox : de la traversée de chemin au RCE
- L’accès de Python au système de fichiers Windows pouvait utiliser l’API wide ou l’API ANSI selon que la chaîne était wide ou narrow
- Après PEP 529, l’encodage du système de fichiers Windows a été standardisé sur UTF-8
- Python 2 et Python 3 avant Python 3.6 restaient vulnérables aux attaques WorstFit
- Cuckoo Sandbox est une plateforme automatisée d’analyse de malware, et sa dernière version officielle dépend de Python 2.7
- Cuckoo se compose de Cuckoo Host et d’un cluster de VM
- Les échantillons téléversés sont exécutés de manière isolée dans une VM, et les paquets réseau, fichiers déposés et logs sont synchronisés par son propre mécanisme
- Si un malware crée un fichier déposé avec un nom de fichier Unicode, une traversée de chemin peut se produire dans le traitement des chemins Python du Cuckoo Host
- Le PoC d’exemple crée le chemin
AAAA\u00a5..\u00a5..\u00a5..\u00a5..\u00a5..\u00a5conf\u00a5cuckoo.conf - Une fois l’analyse terminée, lorsque l’utilisateur clique sur le bouton de téléchargement dans l’interface web, une opération de fichier Python est déclenchée
- Le Cuckoo Host traite alors un chemin converti contenant
../et peut envoyer des données sensibles à l’attaquant
- Le PoC d’exemple crée le chemin
- L’attaquant peut télécharger
cuckoo.confet collecter des informations sensibles nécessaires au calcul du PIN Flask, afin d’obtenir un RCE sur le Sandbox Host- La vidéo de démonstration est disponible ici : Video 11
Argument Splitting : le Best-Fit qui modifie l’analyse de la ligne de commande
- Argument Splitting est une attaque où la chaîne de ligne de commande est modifiée puis les arguments sont séparés, via la sortie de
GetCommandLineAou le chemin non-Unicodeint main()- API et chemins concernés :
GetCommandLineA,int main() - Pages de code affectées : 874, 125x, 932 (JP), 949 (KR)
- Caractères menaçants :
"U+FF02,\U+FF3C,¥U+00A5 (JP),₩U+20A9 (KR)
- API et chemins concernés :
- Un exemple en PHP exécute
wget.exe -qaprès avoir encapsulé l’URL de façon sûre avecescapeshellarg(), mais l’entrée" --use-askpass=calc "permet d’exécutercalc.exe- La même entrée ne se trouve pas bloquée non plus en remplaçant le code par Node.js, Rust ou Python
- Cela fonctionne aussi dans un exemple récent de Python avec
subprocess.run(["wget", "-q", ...])
- Windows transmet la ligne de commande complète à un nouveau processus sous la forme d’une seule chaîne, que l’exécutable analyse lui-même
- Contrairement aux systèmes de type UNIX, il ne s’agit pas d’une structure où un tableau d’arguments est toujours transmis
- L’API
CreateProcessreçoit directement le paramètrelpCommandLine
- Dans l’analyse habituelle d’une ligne de commande, les caractères importants sont l’espace et la tabulation, le guillemet double et le backslash
- L’espace et la tabulation séparent les arguments quand le mode quote n’est pas actif
"bascule le mode quote\échappe le guillemet double et le backslash dans certaines séquences
- Les bibliothèques standard de la plupart des langages échappent les arguments utilisateur selon ces règles, mais l’échappement se termine avant la conversion Best-Fit
- En PHP,
escapeshellargremplace le guillemet double par un espace, entoure l’argument de guillemets et traite le backslash - En Python,
subprocessutiliselist2cmdlinepour échapper selon les règles d’analyse de ligne de commande du CRT Microsoft - Ensuite, si lors de la conversion ANSI
"U+FF02 devient"U+0022, la syntaxe initiale de la ligne de commande est modifiée
- En PHP,
- Les programmes qui n’utilisent que
int main()peuvent aussi être vulnérables- Le compilateur génère
mainCRTStartupdans le binaire, et cette fonction de démarrage est liée à la bibliothèque CRT - Si l’intérieur de la CRT récupère et analyse la ligne de commande via une API ANSI, la conversion Best-Fit entre en jeu
- À cause de ce comportement, il est difficile de bloquer complètement l’attaque avec la seule bibliothèque standard d’un langage donné
- Le compilateur génère
Cas réels d’Argument Splitting
- ElFinder est un gestionnaire de fichiers web open source avec backend PHP, qui prend en charge par défaut les serveurs Windows ainsi que la création et l’extraction d’archives
- Le traitement des archives est implémenté via l’exécution de commandes shell, et les arguments sont échappés avec
escapeshellarg - Le format tar est traité avec
tar.exe, intégré à Windows - Un nom de fichier tar comme
aaa" "--use-compress-program=calc" "bbb.tarpermet d’injecter l’argument--use-compress-programet d’exécuter une commande arbitraire - La démonstration est faite sur un serveur Windows configuré en anglais, avec la page de code 1252, et il est indiqué que cela devrait aussi fonctionner sur les pages de code 125x et la page de code 874
- La vidéo de démonstration est disponible ici : Video 12
- Le traitement des archives est implémenté via l’exécution de commandes shell, et les arguments sont échappés avec
- Dans le cas du
plink.exemodifié utilisé par TortoiseGit, l’injection d’un URI malveillant dans l’entrée de clone peut déclencher une exécution de code- Les détails sont disponibles dans cette liste curatée
- La vidéo de démonstration est Video 13
- RStudio prend en charge la gestion de versions SVN, et si un projet SVN se trouve dans un dossier malveillamment préparé, un seul clic peut lancer la calculatrice
- Les détails sont disponibles dans cette liste curatée
- La vidéo de démonstration est Video 14
- Le cas de Microsoft Excel est CVE-2024-49026, qui combine Argument Splitting et la fonction « Open-With » de Windows
- Windows maintient une table de handlers par extension de fichier, que l’on peut vérifier avec
ftypeetassoc - Comme le nom de fichier devient une partie des arguments du programme handler, l’attaque peut être appliquée via le nom de fichier
- Un nom de fichier où les points, slashs, backslashes et guillemets doubles sont remplacés par leurs formes fullwidth provoque une injection d’arguments dans
Excel.exe - Excel lui-même n’ayant pas d’arguments adaptés à une exploitation supplémentaire, NTLM Relay et RBCD/ADCS sont utilisés ensemble pour obtenir un RCE
- La vidéo de démonstration est Video 15
- Windows maintient une table de handlers par extension de fichier, que l’on peut vérifier avec
Confusion autour des variables d’environnement
- La confusion autour des variables d’environnement survient lorsque
GetEnvironmentVariableA,GetEnvironmentStringsAetchar *getenv()renvoient une version convertie en Best-Fit des variables d’environnement- Les pages de code affectées et les caractères problématiques ne sont pas spécifiés
- Dans le cas d’Apache HTTPd, la plage 0x00-0xFF est concernée
- Pour que cette attaque fonctionne, les variables d’environnement doivent pouvoir être contrôlées par l’utilisateur
- C’est notamment le cas lorsqu’un processus parent transmet des informations à un processus enfant qu’il crée
- En CGI, une grande partie des informations de la requête HTTP, comme la query string et les en-têtes HTTP, est transmise via des variables d’environnement
- L’exemple de contournement de WAF traite d’un scénario où le script CGI agit comme un service de routage
- La configuration Apache contient une règle qui rejette
REQUEST_URIs’il inclut/admin, afin de bloquer l’accès distant à/cgi.pl/admin - En raison du comportement WorstFit de Perl sous Windows, il est possible de contourner cette règle en remplaçant une partie de
adminpar un équivalent Best-Fit - Dans la page de code 1250,
àU+00E0 est transformé enalors de la conversion ANSI - Une requête vers
/cgi.pl/%E0dminapparaît comme un chemin différent pour les règles côté serveur, mais si le script CGI Perl litPATH_INFOvia une API ANSI, il la traite comme/admin
- La configuration Apache contient une règle qui rejette
- Avec PHP-CGI sur Windows, un oracle de présence de fichier et une potentielle LFI ont été observés dans certaines configurations
- Le problème vient de la manière dont
PATH_INFOet d’autres variables d’environnement liées au chemin sont traités - Une requête vers
/index.php/foo/barest transmise par Apache via des variables d’environnement commeREDIRECT_URL,REQUEST_URI,PATH_INFOetPATH_TRANSLATED - À partir de ces seules informations, il est difficile de distinguer clairement le nom du fichier PHP de la partie
PATH_INFOajoutée, et c’estphp-cgi.exequi en fait l’interprétation
- Le problème vient de la manière dont
- Dans une page de code japonaise, l’utilisation de
¥entraîne une divergence d’interprétation du chemin entre le serveur web et PHP-CGI- Le serveur web traite l’ensemble de
/..¥..¥windows/win.ini/foocomme unPATH_INFOsupplémentaire - PHP-CGI reçoit une valeur convertie comme
REQUEST_URI=/index.php/..\\..\\windows/win.ini/foo, ce qui crée une confusion lorsqu’il tente de distinguer le fichier PHP réel dePATH_INFO - Avec Apache, la différence de réponse entre un fichier inexistant et un fichier existant permet un oracle de présence de fichier
- Sous IIS, si la directive
doc_rootest définie, un chemin comme/index.php/..¥..¥..¥windows/win.ini/permet d’inclure et de lireC:\Windows\win.ini, ce qui constitue une LFI - Si le fichier inclus est exécutable ou contient du code contrôlé par l’utilisateur, cela peut potentiellement mener à une RCE, mais ce scénario est classé comme un bug plutôt rare dans des applications réelles
- Le serveur web traite l’ensemble de
Difficultés de la divulgation et de la correction
- L’équipe de recherche a signalé plusieurs problèmes dans des langages de programmation, des projets open source et des programmes CLI intégrés à Windows à leurs maintainers upstream respectifs
- Le plus gros débat a porté sur Argument Splitting
- Certains éditeurs estiment que le simple fait de transmettre une entrée utilisateur à la ligne de commande constitue déjà la vulnérabilité
- L’attribution des responsabilités posait aussi problème
- Le code en cause s’étend entre
mainCRTStartup(), inséré automatiquement à la compilation, et les appels d’API ANSI internes à MSVCRT/UCRT - Il est difficile de déterminer si le problème vient du fait que le développeur n’a pas utilisé
wmain(), ou du fait que le CRT découpe mal la ligne de commande et transmet des arguments incorrects àmain() - Certains projets ne fournissent que le code source, tandis que les exécutables Windows précompilés sont distribués par des bénévoles tiers sur Internet
- Le code en cause s’étend entre
- La correction ne consiste pas simplement à remplacer
main()par une version wide-character- Dès que la signature de fonction change, il faut réécrire les définitions de variables et la logique de parsing des arguments de
char *verswchar_t * - Ce processus est pénible et propice aux erreurs
- Dès que la signature de fonction change, il faut réécrire les définitions de variables et la logique de parsing des arguments de
- Curl a répondu qu’il s’agissait d’une fonctionnalité de Windows et n’a pas prévu de correctif, mais la version de Curl portée par Microsoft modifie l’entrée en
wmain(), si bien que lecurl.exeintégré à Windows n’est pas affecté- Les binaires officiels de Curl sont affectés par l’attaque d’Argument Splitting
- Le rapport complet est public sur HackerOne
- OpenSSL peut traiter les arguments au format wide character via la variable d’environnement
OPENSSL_WIN32_UTF8- Son objectif initial était de corriger les problèmes d’affichage UTF-8 dans l’interface, mais elle atténue aussi l’attaque d’Argument Splitting
- Dans un usage standard d’OpenSSL, les développeurs ignorent souvent qu’ils doivent définir cette variable d’environnement, et l’argument
-enginepeut permettre une exécution de code arbitraire
- La distribution officielle de Perl ne fournit pas d’exécutables Windows précompilés, et des installateurs tiers comme Strawberry Perl et ActiveState Perl sont couramment utilisés
- Ces deux distributions sont affectées par l’attaque d’Argument Splitting
- Après discussion avec les maintainers de Perl, la conclusion a été qu’il s’agit « davantage d’un bug Microsoft que d’un bug Perl », et le problème reste non résolu à ce jour
- Trois cas ont été signalés à Microsoft via le MSRC, et tous ont d’abord été rejetés comme n’atteignant pas le seuil de gravité
- Après plusieurs relances, seul le cas d’Excel a été accepté, à la troisième tentative
- Les autres cas restent non résolus à ce jour
- Le MSRC a répondu que ces cas dépendent d’une vulnérabilité où une application distincte exécute une ligne de commande contenant une entrée non fiable, et que la technique qui rend cette exploitation possible ne remplit pas, en elle-même, les critères d’une vulnérabilité
- De l’aide a aussi été demandée au CERT/CC, et Microsoft a ajouté quelques mois plus tard un avertissement de sécurité à la documentation de
GetCommandLineA- L’avertissement n’a été ajouté qu’à
GetCommandLineA, alors que d’autres API ANSI demandant de la prudence subsistent
- L’avertissement n’a été ajouté qu’à
Cibles affectées signalées et état
- Les éléments identifiés et signalés durant le processus de divulgation sont les suivants
- 2024/05/07: PHP
php-cgi.exe— CVE-2024-4577 - 2024/06/13: Curl Official Build — Won’t Fix
- 2024/06/13: Apache Subversion
svn.exe— CVE-2024-45720 - 2024/06/16: Microsoft Tar
tar.exe— Won’t Fix - 2024/06/19: Microsoft Excel
excel.exe— CVE-2024-49026 - 2024/06/19: Microsoft PhoneBook
rasphone.exe— Won’t Fix - 2024/06/19: Oracle Java
java.exe— Correctif en attente - 2024/06/19: Perl
perl.exe— Won’t Fix - 2024/07/15: Perforce
p4.exe— CVE-2024-8067 - 2024/08/05: PostgreSQL
psql.exe— Won’t Fix - 2024/08/08: Putty
plink.exe— Corrigé - 2024/08/19: OpenSSL
openssl.exe— Other - 2024/08/19: wkhtmltopdf
wkhtmltopdf.exe— EOL - 2024/08/19: GNU Wget — Pas de réponse
- 2024/05/07: PHP
Mesures d’atténuation et surface d’attaque restante
- L’attaque WorstFit étant un problème au niveau du système d’exploitation, des problèmes similaires pourraient continuer à réapparaître tant que Microsoft n’activera pas UTF-8 par défaut sur toutes les éditions de Windows
- Les utilisateurs peuvent vérifier et activer l’option UTF-8 de Windows
- Cette fonctionnalité est encore indiquée comme étant en bêta, et ses effets secondaires éventuels ne sont pas certains
- Les développeurs devraient utiliser autant que possible la Wide Character API
- Le CRT fournit aussi des versions wide character comme
_wgetcwdet_wgetenv - Si des chemins non-wide continuent d’être utilisés, l’implémentation interne peut appeler l’API ANSI et exposer au risque d’attaque WorstFit
- Le CRT fournit aussi des versions wide character comme
- En raison de la rétrocompatibilité de Windows, il pourrait y avoir d’autres endroits où l’API ANSI se cache
- Par exemple, des requêtes du registre Windows comme
RegQueryValueApourraient être affectées, mais il faut encore identifier des scénarios de vulnérabilité - L’équipe de recherche a également observé un comportement Best-Fit dans Active Directory
- Par exemple, des requêtes du registre Windows comme
1 commentaires
Commentaires sur Hacker News
C’est un problème assez épineux. Le mappage de code « best fit » de Microsoft est un convertisseur, documenté mais en pratique assez « au feeling », qui transforme un large éventail de caractères Unicode en ASCII, et il est présent un peu partout dans le système
Ce convertisseur est lié par défaut dans énormément d’endroits et, vu la manière dont Microsoft gère la rétrocompatibilité, il semble inévitable qu’il reste inclus. Les exploits viennent généralement du fait que des points de code inhabituels sont mappés de manière « intuitive » vers des slashs, des tirets ou des guillemets. À l’intérieur d’un langage moderne, ils sont validés comme de l’Unicode correct, mais une fois passés à une commande shell ou à l’API Win32, après transfert du contrôle ils sont réduits différemment. Comme l’a dit le mainteneur de curl, ici « curl est la victime », mais la question est de savoir qui est le coupable. Si le serveur déforme différemment l’entrée utilisateur entre la validation et le passage à une bibliothèque système, le problème finit forcément par apparaître. Une option pour désactiver la conversion best fit côté Win32 pourrait être une solution, mais je ne suis pas spécialiste de Windows, donc ce n’est qu’une supposition. Même dans ce cas, il faudrait encore interagir avec des API officielles ou des logiciels qui ne l’ont pas encore désactivée
"w"plutôt que"a". Cette approche résout aussi le problème des chemins de plus de 260 caractères si on ajoute le préfixe"\\?\"ou qu’on configure correctement le manifeste, et c’est possible et recommandé depuis Windows XPJe ne comprends pas vraiment pourquoi les API non-Unicode restent encore aussi utilisées. Il est difficile d’imaginer que ce soit par volonté de prendre en charge Windows 98 ou Windows 2000
Ce qu’il faudrait aussi, c’est une forme de linting. Dans une application moderne, il n’y a généralement aucune raison d’appeler les fonctions ANSI de la WinAPI. On pourrait aussi définir les paramètres régionaux en UTF-8 et n’utiliser que les fonctions 8 bits, mais je ne sais pas à quel point cela fonctionne bien. Il me semble qu’il existe aussi quelques réglages et en-têtes permettant de faire fonctionner
argv,printfetstd::couten UTF-8, et de n’utiliser pour la WinAPI que des fonctions de conversion UTF-8/UTF-16 sans transformations bizarres. Microsoft devrait documenter ces procédures en un seul endroitC’était dans une certaine mesure prévisible, mais même pour quelqu’un qui a fait une dizaine d’années de développement Windows et de bidouillage d’API Wine à l’époque où la confusion W/A était courante, c’était nouveau
Windows ressemble au jeu de cartes Munchkin : quand plusieurs fonctionnalités s’imbriquent par hasard, elles peuvent se combiner en des exploits incroyablement aléatoires et puissants. Le fait que le sous-système ANSI soit en train d’être converti en UTF-8 est une bonne nouvelle et, en théorie, cela pourrait atténuer beaucoup de ces problèmes. Je me demande aussi si l’équipe Rust devra apporter encore une autre correction à son API de création de processus
Bien sûr, Rust ne peut pas contrôler ce qui se passe au-delà des frontières du processus. Si l’application lancée par Rust utilise des API ANSI, c’est elle qui aura un problème, mais c’est la responsabilité de cette application
Si je me souviens bien, la suppression progressive d’ANSI et la recommandation d’utiliser les API Wide Character étaient la position officielle de Microsoft depuis NT 3.5
Malheureusement, l’un des gros obstacles est l’implémentation de
msvcrt.dll, la bibliothèque runtime C/C++ de Microsoft. Les fonctions wide non standard comme_wfopen()ou_wgetenv()utilisent en interne les fonctions W de la WinAPI, mais les fonctions narrow standard commefopen()ougetenv()appellent directement les fonctions A au lieu de les convertir vers leurs équivalents wide. Et les fonctions A ne signalent généralement pas les échecs de conversion Unicode et les écrasent simplement avec du best fit. Les personnes qui portent un logiciel écrit en C vers Windows n’ont pas forcément envie de remplacer partout l’usage des fonctions standard par des fonctions non portables de Microsoft. À partir de là, c’est pratiquement une réécriture complèteactiveCodePagesur UTF-8 dans le manifeste de l’application et d’utiliser uniquement les fonctions « ANSI »#defineles fonctions standard commemainetfopenvers leurs équivalents wide quand on compile pour WindowsCela empêche d’utiliser simplement
char*et les littéraux de chaîne bruts, donc on définit un typetchar, qui estcharsous Linux etwchar_tsous Windows, ainsi qu’une macro_T()pour les littéraux de chaîne. En général, ça fonctionne sans trop y penser-Aqui apparaît en premier plutôt que la variante-W. Je ne sais pas s’il y a quelque chose de bizarre dans lerobots.txt, mais c’est étrange qu’une API recommandant d’utiliser la variante-Wpour le nouveau code renvoie par défaut l’API legacymsvcrt.dllde Microsoft a été remplacée par la Universal C Runtime (UCRT)[1], et l’UCRT est conforme à C99Il existe deux façons de forcer réellement la page de code « Ansi » en UTF-8 pour une application que l’on a écrite soi-même ou pour un EXE patché
L’une consiste à utiliser un fichier manifeste, ce qui fonctionne depuis certaines builds de Windows 10. On peut aussi l’appliquer à n’importe quel EXE après compilation, ce qui permet d’imposer de force la prise en charge UTF-8 à un programme. C’est particulièrement utile pour les programmes en mode console. L’autre consiste à utiliser le type de hack employé par les outils de type « App Locale ». Une méthode inclut l’appel à des fonctions non documentées de NTDLL. Je ne sais pas exactement quelles fonctions sont nécessaires, mais
RtlInitNlsTablesetRtlResetRtlTranslationspourraient être impliquéesIl est difficile de dire si Microsoft pourra activer UTF-8 par défaut dans toutes les éditions de Windows. Beaucoup d’anciennes applications risqueraient de se casser, car elles supposent une page de code donnée ou 1 octet par caractère
Plus subtilement, certaines applications réutilisent aussi des tampons existants en supposant que le nombre d’octets n’augmente pas lors d’une conversion de caractères wide vers ANSI. Ce n’est pas vrai avec UTF-8, et c’était généralement vrai avec la plupart des anciennes pages de code, ce qui pourrait créer de nouvelles vulnérabilités. Il semblerait bien moins casseur de supprimer la logique Best-Fit des API Win32
xxxAet de remplacer les caractères non convertibles par un caractère commex, sans signification méta commune[0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
Vu les problèmes causés par le mappage Best-Fit, il serait aussi raisonnable d’en faire la valeur par défaut, mais Microsoft devrait aider les utilisateurs à trouver un moyen d’exécuter facilement les vieux logiciels. Une méthode moins raisonnable consisterait à supprimer du mappage Best-Fit tous les mappages vers des caractères ASCII « spéciaux », mais cela n’aiderait pas les applications qui lient statiquement le CRT. Et comme cela ne corrigerait pas la vulnérabilité, ce ne serait pas une bonne solution. Parfois, une faille de sécurité sert de motivation pour imposer une rupture de compatibilité ascendante
Microsoft connaît ce problème depuis au moins un an. Ils ont publié une règle spéciale d’analyse de code, CA2101[1], qui déconseille explicitement l’usage du mappage best-fit
La description de la règle mentionnait une vulnérabilité de sécurité, mais en restait délibérément vague sur les détails
[1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...
Il n’est pas nécessaire de tout remplacer de
char *parwchar *. Il suffit de convertir les caractères wide reçus en UTF-8, ou, si l’on veut accepter même des séquences invalides comme des surrogates non appariés, en quelque chose comme WTF-8 de Rust, puis de continuer à utilisercharBien sûr, il faut veiller à ne pas mélanger des chaînes ANSI ou OEMCP avec des chaînes UTF-8, mais si l’on n’utilise que UTF-8, c’est simple. C’est l’approche recommandée par le site classique https://utf8everywhere.org/
J’évitais ce bug par hasard depuis quelques années en ayant activé le mode UTF-8 sur mon PC Windows personnel. C’est le réglage mentionné en bas de l’article
Je l’avais activé parce que d’anciens jeux étrangers affichaient des caractères corrompus, et même si c’est marqué « Beta », je n’ai remarqué ni bug ni effet secondaire
Je me demandais si cette case beta était équivalente au fait de définir
ActiveCodePagesur UTF-8 dans le manifeste, mais la documentation[0] indique clairement que GDI ne suit pas une page de code par processus et ne suit que l’unique page de code globale définie par cette caseC’est un peu dommage qu’on ne puisse pas faire un opt-in totalement UTF-8 pour son app avec les API
*A. Cela dit, je pense que cela peut quand même rester un contournement valable ou une mesure de défense en profondeur pour les problèmes soulignés dans l’article[0] https://learn.microsoft.com/en-us/windows/apps/design/global...
Mon dieu. Je savais que l’API Windows proposait ce type de conversion best-fit, mais je ne savais pas que c’était le comportement par défaut de nombreuses fonctions ANSI avec ma page de code par défaut, 949[1]
À ce stade, cela devrait simplement être interdit, comme
gets. [1] Je sais qu’il existe la page de code UTF-8 65001. Pendant longtemps, elle a été franchement inutilisable, et elle pose encore aujourd’hui des problèmes de compatibilité.