WorstFit: Windows ANSIに潜む隠れたTransformerを公開
(blog.orange.tw)- WindowsのBest-Fit文字変換は、UTF-16文字列をANSIコードページに変換する過程で見た目の似た文字に置き換え、この挙動がPath Traversal、Argument Injection、RCEにつながるWorstFit攻撃面となる
- 問題は、ANSI API、C/C++ランタイム、コンパイラが挿入するスタートアップコード、開発者による非wide文字APIの使用が重なる構造で発生し、
GetCommandLineA、GetEnvironmentVariableA、getenv、int main()の経路が影響を受ける - CVE-2024-4577では、Chinese/JapaneseコードページでU+00ADが
-に変換され、PHP-CGIのパッチを回避したほか、Filename Smugglingでは¥、₩、全角スラッシュが/または\\に変換され、パスの混同を生む - Argument Splittingでは、全角ダブルクォートやYen/Won記号がコマンドラインの解析文字を生成し、
wget.exe、tar.exe、openssl.exe、java.exeのようなCLIツールに引数を注入でき、PHP・Python・Node.js・Rustの一般的な引数エスケープだけでは防ぎにくい - 緩和するには、WindowsのUTF-8オプションを有効にするか、開発者がWide Character APIと
_wgetcwd、_wgetenv、wmain()のようなwide文字経路を使う必要があり、MicrosoftがすべてのWindowsエディションでUTF-8を既定で有効にするまで、類似の問題は繰り返される可能性がある
Windowsのエンコーディング構造とBest-Fit
- Windowsは初期にANSIコードページを使用しており、言語圏に応じて1252、932、936、949、950といったコードページが異なっていた
- ACP(ANSI Code Page)は、ファイル操作や環境変数など、ほとんどのアプリケーションとシステム設定で使われる
- OEMCP(Original Equipment Manufacturer Code Page)は、主にコンソールの読み書きのようなデバイス通信に使われる
chcpはACPではなくOEMCPを表示するため、この研究の中心であるACPを確認する手段ではない
- Windowsは1990年代半ばにUnicodeへ移行し、現在の中核APIはUTF-16ベースのwide characterを使用する
- ファイルシステム、システム情報、テキスト処理といった中核APIがwide文字APIへ移行した
- UTF-8機能は存在するが、大半の言語では既定で有効になっておらず、記事ではbeta段階と表現されている
- 下位互換性のため、Windows APIはANSI版とUnicode版を併せて提供している
- ANSI APIは
GetEnvironmentVariableAのようにA接尾辞を持つ - Unicode APIは
GetEnvironmentVariableWのようにW接尾辞を持つ - ANSI APIが呼び出されると、Windowsは内部UTF-16文字列を
RtlUnicodeStringToAnsiStringまたはWideCharToMultiByteでANSI文字列に変換する
- ANSI APIは
Best-FitがWorstFitになる仕組み
- Best-Fitは、UTF-16文字を対象のANSIコードページで正確に表現できないとき、見た目が似ている、または近いと感じられる文字へマッピングする挙動である
- たとえばWindows-1252では
∞U+221Eは8にマッピングされる √π⁷≤∞がANSI APIを通ると、"vp7=8"のように変わり得る
- たとえばWindows-1252では
- マッピングはコードページごとに異なる挙動を示す
¥U+00A5はJapanese 932コードページで\\にマッピングされる- Central European 1250コードページでは
Yにマッピングされる - そのほか大半のコードページでは変更されない
- Windows APIの直接呼び出しだけでなく、CRT関数や一般的な
main関数経路でも同じ変換が発生するgetenvのようなnon-wide CRT関数でBest-Fit変換が適用されるint main(int argc, char* argv[], char* envp[])の形で引数や環境変数を受け取る場合にも変換が介在する- これは、コンパイラが挿入するCRTスタートアップコードとANSI Windows APIの使用が組み合わさるためである
- マッピングの確認にはBest-fit Mapping GrepperとUnicode.orgのWindowsBestFit生マッピングデータを参照できる
最初のWorstFit事例: PHP-CGI CVE-2024-4577
- CVE-2024-4577は、Chinese/Japaneseコードページに設定されたPHP-CGIサーバーを
?%ADsリクエストだけで侵害できるWorstFit攻撃の事例である- 影響を受けるコードページは932(Japanese)、936(Simplified Chinese)、950(Traditional Chinese)
- 脅威となる文字は
U+00AD
- 2012年のPHP-CGI脆弱性は、Apacheがクエリ文字列をCGIプログラムの最初の引数として自動処理したことで発生したargument injectionだった
?-sを付けるとページのソースコード漏えいとRCEが可能だった- PHPのパッチは、クエリ文字列がダッシュで始まる場合に引数解析を中断する方式だった
- Best-Fitにより、U+00AD soft hyphenがChinese/Japaneseコードページで
-に変換され、既存のパッチが回避された?%ADsはPHP-CGIの観点では-sのように動作し得る- この事例をきっかけに、研究チームはBest-Fitという用語に初めて出会った
Filename Smuggling: パス文字が変換される問題
- Filename Smugglingは、ファイル名に含まれるUnicode文字がANSI API経路で
/または\\に変換され、パストラバーサルを生み出し得る攻撃である- 関連APIは
GetCurrentDirectoryA、getcwd、FindFirstFileA、findfirst*、GetFullPathNameAなど - 影響を受けるコードページは874、125x、932(JP)、949(KR)
- 脅威となる文字は
/U+FF0F、\U+FF3C、¥U+00A5(JP)、₩U+20A9(KR)
- 関連APIは
- Chrome V8のDeveloper Shellである
d8.exeは、内部実装でGetCurrentDirectoryA()を使って現在の作業ディレクトリを取得する- 悪意あるUnicode文字を含む作業ディレクトリを作成できると、ANSI APIアクセス時にパストラバーサルのペイロードへ変化する
- 例として、意図しない
C:/windows/win.iniへのアクセスが可能になる
- mrubyの
Dir.getwd()のWindows実装は、ANSI CRT関数_getcwd()に依存している- 戻り値が汚染される可能性があり、Path Traversalにつながり得る
Cuckoo Sandbox: Path TraversalからRCEまで
- PythonのWindowsファイルシステムアクセスでは、文字列がwideかnarrowかに応じてwide APIまたはANSI APIを使用できた
- PEP 529以降、WindowsファイルシステムのエンコーディングはUTF-8に標準化された
- Python 2およびPython 3.6以前のPython 3は、WorstFit攻撃に対して脆弱なままだった
- Cuckoo Sandboxは自動化されたマルウェア分析プラットフォームであり、最新の公式バージョンはPython 2.7に依存している
- CuckooはCuckoo HostとVM Clusterで構成される
- アップロードされたサンプルはVMで隔離実行され、ネットワークパケット・ドロップファイル・ログを独自メカニズムで同期する
- マルウェアがUnicodeファイル名を持つドロップファイルを作成すると、Cuckoo HostのPythonパス処理でPath Traversalが発生する可能性がある
- 例のPoCは
AAAA\\u00a5..\\u00a5..\\u00a5..\\u00a5..\\u00a5..\\u00a5conf\\u00a5cuckoo.confパスを生成する - 分析終了後、ユーザーがWebインターフェースでダウンロードボタンを押すと、Pythonのファイル操作がトリガーされる
- Cuckoo Hostは変換後の
../を含むパスを処理し、攻撃者に機密データを送る可能性がある
- 例のPoCは
- 攻撃者は
cuckoo.confをダウンロードし、Flask PINの計算に必要な機密情報を収集して、Sandbox Host上でRCEを達成できる- デモ動画は Video 11 で提供されている
Argument Splitting: コマンドライン解析を変えるBest-Fit
- Argument Splitting は、
GetCommandLineAの出力またはnon-Unicodeなint main()経路でコマンドライン文字列が変化し、引数が分割される攻撃である- 関連APIと経路は
GetCommandLineA、int main() - 影響を受けるコードページは874、125x、932(JP)、949(KR)
- 脅威となる文字は
"U+FF02、\U+FF3C、¥U+00A5(JP)、₩U+20A9(KR)
- 関連APIと経路は
- 例のPHPコードは
escapeshellarg()でURLを安全に囲んだうえでwget.exe -qを実行するが、入力" --use-askpass=calc "によりcalc.exeの実行が可能になる- 同じ入力はNode.js、Rust、Pythonに置き換えても防げない
- Python最新バージョンの
subprocess.run(["wget", "-q", ...])の例でも動作する
- Windowsは新しいプロセスにコマンドライン全体を1つの文字列として渡し、実行ファイルがこれを直接解析する
- UNIX系のように引数配列が常に渡される構造ではない
CreateProcessAPIはlpCommandLineパラメータを直接受け取る
- 一般的なコマンドライン解析で重要な文字は、空白・タブ、double quote、backslashである
- 空白とタブはquote modeでないときに引数を分割する
"はquote modeを切り替える\\は特定のシーケンスでdouble quoteとbackslashをescapeする
- 多くの言語の標準ライブラリはこの規則に合わせてユーザー引数をエスケープするが、エスケープはBest-Fit変換の前に完了する
- PHPの
escapeshellargはdouble quoteを空白に置き換え、引数をquoteで囲み、backslashを処理する - Pythonの
subprocessはlist2cmdlineによりMicrosoft CRTのコマンドライン解析規則に合わせてエスケープする - その後のANSI変換で
"U+FF02が"U+0022 に変わると、元のコマンドライン構文が変化する
- PHPの
int main()のみを使うプログラムも脆弱でありうる- コンパイラは
mainCRTStartupをバイナリに生成し、この開始関数がCRTライブラリとリンクされる - CRT内部がANSI APIでコマンドラインを取得して解析すると、Best-Fit変換が介入する
- この動作のため、特定のプログラミング言語の標準ライブラリだけでは攻撃を完全に防ぐのが難しい
- コンパイラは
Argument Splittingの実例
- ElFinderはPHPバックエンドベースのオープンソースWebファイルマネージャーで、デフォルトでWindowsサーバーと圧縮の作成・展開をサポートする
- アーカイブ処理はshell commandの実行として実装されており、引数は
escapeshellargでエスケープされる - tar形式の処理にはWindows内蔵の
tar.exeが使用される aaa" "--use-compress-program=calc" "bbb.tarのようなtarファイル名で--use-compress-program引数を注入し、任意コマンド実行が可能になる- デモはEnglish-configured Windows server、Code Page 1252基準で、125xコードページとCode Page 874でも動作するはずだとまとめられている
- デモ動画は Video 12 で提供されている
- アーカイブ処理はshell commandの実行として実装されており、引数は
- TortoiseGitで使われる修正済み
plink.exeの事例では、悪意のあるURIをclone入力として与えるとコード実行をトリガーできる- 詳細は curated list で確認できる
- デモ動画は Video 13
- RStudioはSVNバージョン管理をサポートしており、悪意を持って作成されたフォルダにSVNプロジェクトがあると、クリック1回で電卓を起動できる
- 詳細は curated list で確認できる
- デモ動画は Video 14
- Microsoft Excelの事例は、Argument SplittingとWindowsの「Open-With」機能を組み合わせた CVE-2024-49026 である
- Windowsはファイル拡張子ごとのhandler tableを維持しており、
ftypeとassocで確認できる - ファイル名はhandlerプログラムの引数の一部になるため、ファイル名を通じて攻撃を適用できる
- dots、slash、backslash、double quoteをfullwidth形式に置き換えたファイル名により、
Excel.exeへのargument injectionを引き起こす - Excel自体には追加悪用に適した引数がないため、NTLM RelayとRBCD/ADCSを併用してRCEを達成する
- デモ動画は Video 15
- Windowsはファイル拡張子ごとのhandler tableを維持しており、
環境変数の混同
- Environment Variable Confusion は、
GetEnvironmentVariableA、GetEnvironmentStringsA、char *getenv()が環境変数の Best-Fit 変換版を返すことで発生する- 影響を受けるコードページと脅威文字は特定されていない
- Apache HTTPd の事例では 0x00-0xFF が関連する
- この攻撃が成立するには、環境変数がユーザーによって制御可能である必要がある
- 親プロセスが生成した子プロセスに情報を渡す場合がこれに当たる
- CGI では、query string、HTTP header など HTTP リクエスト情報の多くが環境変数として渡される
- WAF 回避の例は、CGI スクリプトが routing service のように動作する状況を扱う
- Apache 設定には、
/adminを含むREQUEST_URIを拒否して/cgi.pl/adminへのリモートアクセスを防ぐルールがある - Windows Perl の WorstFit 動作により、
adminの一部を Best-Fit equivalent に置き換えると回避できる - Code Page 1250 では、
àU+00E0 は ANSI 変換中にaに変わる /cgi.pl/%E0dminリクエストはサーバー側ルールでは別のパスに見えるが、Perl CGI スクリプトが ANSI API でPATH_INFOを読むと/adminとして処理される
- Apache 設定には、
- Windows 上の PHP-CGI では、特定の構成で file existence oracle と潜在的な LFI が確認された
- 原因は
PATH_INFOとその他の path 関連環境変数の処理方法にある /index.php/foo/barリクエストは Apache 基準でREDIRECT_URL、REQUEST_URI、PATH_INFO、PATH_TRANSLATEDのような環境変数として渡される- この情報だけでは PHP ファイル名と追加の
PATH_INFOの境界を明確に区別しにくく、php-cgi.exeがこれを解釈する
- 原因は
- Japanese コードページで
¥を使うと、Web サーバーと PHP-CGI のパス解釈が異なる- Web サーバーは
/..¥..¥windows/win.ini/foo全体を追加のPATH_INFOとして処理する - PHP-CGI は
REQUEST_URI=/index.php/..\\..\\windows/win.ini/fooのように変換された値を受け取り、実際の PHP ファイルとPATH_INFOを区別する過程で混同する - Apache では、存在しないファイルと存在するファイルの応答差により file existence oracle が可能になる
- IIS で
doc_rootdirective が設定されている場合、/index.php/..¥..¥..¥windows/win.ini/のようなパスでC:\\Windows\\win.iniを include して読み込む LFI が可能になる - include されたファイルが実行可能だったり、ユーザー制御可能なコードを含んでいたりする場合は潜在的に RCE につながるが、そのシナリオは実アプリケーションではまれな bug に近いものと分類される
- Web サーバーは
公開と修正プロセスの難しさ
- 研究チームは、プログラミング言語、オープンソースプロジェクト、Windows 内蔵 CLI プログラムの複数の問題を各 upstream maintainer に報告した
- 最も大きな議論は Argument Splitting で発生した
- 一部ベンダーは、ユーザー入力をコマンドラインに渡すこと自体が脆弱性だとみなした
- 責任の所在が不明確な点も問題だった
- 問題のあるコードが、コンパイル中に自動挿入される
mainCRTStartup()と MSVCRT/UCRT 内部の ANSI API 呼び出しにまたがっている - 開発者が
wmain()を使わなかったことが問題なのか、CRT がコマンドラインを誤って分割してmain()に誤った引数を渡したことが問題なのか区別しにくい - 一部プロジェクトはソースコードのみを提供し、Windows prebuilt executable はインターネット上の第三者ボランティアが配布している
- 問題のあるコードが、コンパイル中に自動挿入される
- 修正は単に
main()を wide-character 版に置き換えるだけではない- 関数シグネチャが変わると、変数定義と引数パースロジックを
char *からwchar_t *ベースに書き直す必要がある - この過程は苦痛で、エラーが起きやすい
- 関数シグネチャが変わると、変数定義と引数パースロジックを
- Curl は Windows 機能だとして修正計画はないと回答し、Microsoft が移植した Curl は entry を
wmain()に修正しているため、Windows 内蔵のcurl.exeは影響を受けない- Curl 公式ビルドバイナリは Argument Splitting 攻撃の影響を受ける
- 全体報告は HackerOne で公開されている
- OpenSSL は
OPENSSL_WIN32_UTF8環境変数により、引数を wide character 形式で処理できる- 元の目的は UI での UTF-8 表示問題を修正することだったが、Argument Splitting 攻撃の緩和にもなる
- 標準的な OpenSSL 利用では、開発者がこの環境変数を設定すべきことを知らない場合が多く、
-engine引数を使った任意コード実行が可能になる
- Perl 公式配布版は Windows prebuilt executable を提供しておらず、Strawberry Perl や ActiveState Perl のようなサードパーティ製インストーラーが一般的に使われる
- 両配布版は Argument Splitting 攻撃の影響を受ける
- Perl maintainer との議論の結果、「Perl のバグというより Microsoft のバグに近い」という結論となり、現在も未解決のままとなっている
- Microsoft には 3 件が MSRC に報告されたが、いずれも当初は重大度基準を満たさないとして却下された
- 複数回の再オープン後、Excel の事例だけが 3 回目の試行後に受理された
- 他の事例は現在まで未解決のままである
- MSRC は、別アプリケーションが信頼できない入力をコマンドラインに入れて実行する脆弱性に依存しており、それを悪用可能にする technique 自体は脆弱性要件を満たさないと回答した
- CERT/CC にも支援を求め、Microsoft は数か月後に
GetCommandLineAのドキュメントへ セキュリティ警告 を追加した- 警告は
GetCommandLineAにのみ追加され、注意が必要な ANSI API はまだ残っている
- 警告は
報告された影響対象と状況
- 公開プロセスで確認・報告された項目は次の通り
- 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— Pending Fix - 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— Fixed - 2024/08/19: OpenSSL
openssl.exe— Other - 2024/08/19: wkhtmltopdf
wkhtmltopdf.exe— EOL - 2024/08/19: GNU Wget — No Reply
- 2024/05/07: PHP
緩和策と残された攻撃面
- WorstFit攻撃はオペレーティングシステムレベルの問題であるため、MicrosoftがすべてのWindowsエディションでUTF-8をデフォルトで有効にするまでは、類似の問題が今後も繰り返し現れる可能性がある
- ユーザーができる対策は、WindowsのUTF-8オプションを確認して有効にすること
- この機能はまだbeta段階として表示されており、副作用の有無ははっきりしていない
- 開発者は可能な限りWide Character APIを使うべき
- CRTも
_wgetcwd、_wgetenvのようなwide character版を提供している - non-wide経路を使い続けると、内部実装がANSI APIを呼び出す可能性があり、WorstFit攻撃にさらされるおそれがある
- CRTも
- Windowsの下位互換性のため、ANSI APIが潜んでいる場所はほかにもある可能性がある
- 例として
RegQueryValueAのようなWindows Registry queryが影響を受ける可能性はあるが、脆弱なシナリオを見つける必要がある - 研究チームはActive DirectoryでもBest-Fit動作を観測している
- 例として
1件のコメント
Hacker News のコメント
これはかなり厄介な問題だ。Microsoft の「best fit」コードマッピングは、広範な Unicode を ASCII に変換する、公開されてはいるものの実質的には「感覚ベース」のマッパーで、システム全体に組み込まれている。
このマッパーは非常に多くの場所でデフォルトでリンクされており、Microsoft の下位互換性に対する考え方からすると、今後も含まれ続けるように見える。エクスプロイトはたいてい、特殊なコードポイントがスラッシュ、ハイフン、引用符のようなものに「それっぽく」マッピングされることから生じる。現代的な言語の中では正しい Unicode として検査されるが、シェルコマンドや Win32 API に渡ると、制御を引き渡した後で別の方法で縮小変換される。curl の管理者が言うように、ここでは「curl は被害者」だが、犯人が誰なのかが問題だ。サーバーがユーザー入力を検証するときとシステムライブラリに渡すときで違う形につぶしてしまえば、結局問題が起きる。Win32 側で best fit 変換を無効にできる選択肢が解決策になり得るが、Windows の専門家ではないので推測だ。そうしたとしても、公式 API や、まだ無効化していないソフトウェアとは引き続き相互作用することになる。
"a"ではなく"w"で終わる関数を使うことだ。この方法は"\\?\"プレフィックスを付けるか、マニフェストを正しく設定すれば、260 文字超のパス問題も同時に解決し、Windows XP 以降で利用可能で推奨されてきた。非 Unicode API が今でもこれほど広く使われている理由はよく分からない。Windows 98 や Windows 2000 をサポートしたいからだとは考えにくい。
もう一つ必要なのは一種のリンティングだ。現代的なアプリケーションで ANSI WinAPI 関数を呼ぶ理由は通常ない。ロケールを UTF-8 に設定して 8 ビット関数だけを使う方法もあるだろうが、どれほどうまく動くかは分からない。
argv、printf、std::coutが UTF-8 で動作し、変な変換なしに WinAPI 用の UTF-8/UTF-16 変換関数だけを使わせる設定やヘッダーもいくつかあると理解している。こうした手順を Microsoft が一か所に文書化すべきだ。これはある程度予想できることではあるが、W/A の混乱が起きていた頃に 10 年ほど Windows 開発と Wine API のハッキングをしていた身としても新しかった。
Windows はカードゲームの Munchkin のようなもので、複数の機能が偶然かみ合うと、信じがたいほどランダムで強力なエクスプロイトにまとまることがある。ANSI サブシステムを UTF-8 に変えつつある点は歓迎でき、理論上はこうした問題の多くを緩和できるはずだ。Rust チームがプロセス生成 API にさらに別の修正を入れる必要があるのかも気になる。
もちろん Rust がプロセス境界の向こう側で起きることを制御できるわけではない。Rust が実行したアプリケーションが ANSI API を使えばそちらに問題が起きるが、それはそのアプリケーションの責任だ。
「ANSI を段階的に廃止し、Wide Character API の使用を推奨すること」は、記憶が正しければ NT 3.5 の頃から Microsoft の公式方針だった。
残念ながら大きな障害の一つは、Microsoft の C/C++ ランタイムライブラリ
msvcrt.dllの実装方法だ。_wfopen()、_wgetenv()のような非標準の wide 関数は内部的に Win API の W 関数を使うが、fopen()、getenv()のような標準の narrow 関数は wide 版に変換する代わりに A 関数をそのまま使う。そして A 関数は通常、Unicode 変換の失敗を報告せず best-fit 方式で上書きしてしまう。C で書かれたソフトウェアを Windows に移植する人が、標準関数の使用をすべて Microsoft の非移植関数に置き換えたいとは思わない。その時点からは実質的に全面的な書き直しだ。activeCodePageを UTF-8 に設定し、「ANSI」関数だけを使え、という方向だった。mainやfopenのような標準関数を wide 対応関数へ#defineする。こうすると
char*と装飾なしの文字列リテラルをそのまま使うことはできないので、Linux ではchar、Windows ではwchar_tになるtchar型と、文字列リテラル用の_T()マクロを定義する。たいていはあまり考えずにうまく動く。-W変種ではなく-A変種が先に出てくることだ。robots.txtに何か変なものがあるのかは分からないが、新しいコードでは-W変種を使えと推奨している API が、デフォルトでレガシー API を返すのはおかしい。msvcrt.dllは Universal C Runtime(UCRT)[1] に置き換えられており、UCRT は C99 に準拠している。自分で書いたアプリケーションやパッチした EXE で「Ansi」コードページを実際に UTF-8 に強制する方法は二つある。
一つはマニフェストファイルを使う方法で、Windows 10 の特定のビルド以降で動作する。ビルド後の任意の EXE にも適用できるので、プログラムに UTF-8 対応を無理やり入れられる。コンソールモードのプログラムで特に有用だ。もう一つは「App Locale」系のツールが使うハックを使うことだ。ある方法には、NTDLL の文書化されていない関数呼び出しが含まれる。正確にどの関数が必要なのかは分からないが、
RtlInitNlsTablesとRtlResetRtlTranslationsが関係しているかもしれない。Microsoft がすべての Windows エディションで UTF-8 をデフォルトで有効にする可能性は、正直よく分からない。特定のコードページや「1文字あたり1バイト」を前提にした古いアプリケーションが多く、壊れる可能性がある
さらに微妙なのは、ワイド文字から ANSI に変換するときにバイト数が増えないと仮定して、既存のバッファを再利用するアプリケーションもあること。UTF-8 ではそうはならず、従来のコードページの大半ではだいたい成り立っていたため、新たな脆弱性が生まれる可能性がある。むしろ Win32 の
xxxAAPI から Best-Fit ロジックを取り除き、マッピングできない文字を、共通のメタ的意味を持たないxのような文字に置き換える方が、はるかに壊れにくそうだ[0] https://tambre.ee/blog/adobe_after_effects_windows_utf-8/
Best-Fit マッピングが引き起こす問題を見ると、それをデフォルトにするのも合理的だが、Microsoft はユーザーが古いコードを簡単に実行できる方法を見つけられるよう支援する必要があるだろう。あまり合理的でない方法としては、Best-Fit マッピングから「特殊な」ASCII 文字へのマッピングをすべてなくすことがあるが、CRT を静的リンクしたアプリには役に立たない。脆弱性を修正できるわけでもないので、良い解決策ではない。時にはセキュリティ脆弱性が後方互換性の破壊を推し進める動機になる
Microsoft は少なくとも1年前からこの問題を把握していた。CA2101[1] という特別なコード分析ルールを出し、best-fit マッピングの使用を明示的に推奨しないようにしていたからだ
ルールの説明ではセキュリティ脆弱性に言及していたが、詳細は意図的に曖昧にされていた
[1] https://learn.microsoft.com/en-us/dotnet/fundamentals/code-a...
すべてを
char *からwchar *に変える必要はない。受け取ったワイド文字を UTF-8 に変換するか、対になっていないサロゲートのような不正なシーケンスまで許容したいなら Rust の WTF-8 のようなものに変換したうえで、そのままcharを使い続ければよいもちろん ANSI や OEMCP の文字列を UTF-8 文字列と混ぜないよう注意は必要だが、単に UTF-8 だけを使えば簡単だ。古典的な https://utf8everywhere.org/ サイトが推奨しているアプローチはこの方法だ
個人用の Windows PC では数年前から UTF-8 モードを有効にしていたおかげで、このバグを偶然回避できていた。記事の下部に出ている設定だ
古い海外ゲームで文字化けが表示されるため有効にしていたもので、「Beta」と表示されているが、バグや副作用は感じなかった
ベータのチェックボックスが、マニフェストで
ActiveCodePageを UTF-8 に設定することと同じなのか気になっていたが、ドキュメント[0]を見ると、GDI はプロセスごとのコードページには従わず、チェックボックスが設定する単一のグローバルコードページだけに従うと明記されている自分のアプリで
*AAPI に対して完全に UTF-8 へ opt-in できないのは少し残念だ。それでも記事で強調されている問題に対しては、依然として有効な回避策や多層防御の手段になり得ると思う[0] https://learn.microsoft.com/en-us/windows/apps/design/global...
なんてことだ。Windows API がその種の best-fit 変換を提供していることは知っていたが、自分の既定コードページである 949[1] で、複数の ANSI 関数の既定動作になっているとは知らなかった
ここまで来たら
getsのように、単純に禁止すべきだ。[1] UTF-8 コードページ 65001 があることは知っている。長い間、本当に使いものにならないレベルだったし、今でも互換性問題に悩まされている