これは、3 部構成から成るシリーズの最初の記事です。このシリーズでは µC/OS プロトコルスタックのファジングの複雑さについて詳しく説明します。これから説明する手法はさまざまな RTOS 環境全般に適用できますが、主に µC/OS に焦点を当てることにします。
今回は、さまざまな µC/OS コンポーネントに導入した戦略的なコード変更について、その一部をご紹介します。コードに変更を加える目的は、µC/HTTP サーバーに特化したファジングハーネスの開発プロセスを合理化することです。このシリーズのパート 2 では、ファジングのテストケースごとに複数のリクエストを処理するために使用した手法について説明します。パート 3 では、今回と同様に、µC/TCP-IP スタックをファジングする目的で行ったコード変更について説明します。
少し背景を説明しておくと、µC/OS は RTOS、つまり「リアルタイム オペレーティングシステム」です。RTOS は特殊なオペレーティングシステムであり、ハードウェアリソースを管理し、タイミングが極めて重要なシステム(組み込みシステム、医療機器、産業用制御装置など)で実行する必要があるアプリケーションをホストするように設計されています。RTOS は、デスクトップ オペレーティングシステムで実行されるソフトウェアほど徹底的にファジングされていませんが、その主な原因は、RTOS 用のファジングハーネスの開発が複雑なことです。
デスクトップ アプリケーションやライブラリで使用される 1 行の単純なファジングハーネスの開発に通常必要とされるコーディング作業と比べ、RTOS 用のハーネスの開発にはより手間がかかります。
RTOS に何らかの脆弱性があると、さまざまな業界の多くのデバイスに影響を及ぼしかねません。デスクトップ オペレーティングシステムで一般的に行われているのと同じように、RTOS のコードベースを厳格なテストにかけることが重要です。このブログ記事シリーズで説明している手法がきっかけとなって、RTOS ソフトウェアコンポーネントのファジングがさらに広く普及することを願っています。
RTOS コードをファジングする場合、これまでは、カスタムハードウェアをセットアップしてネイティブ環境でコードをファジングするか、システムをエミュレートし、その環境でファジングするかのどちらかでした。しかし、Weston Embedded 社が 2020 年に完全な µC/OS ソースコードを公開したことで、それほど複雑ではないアプローチでこのタイプのコードをファジングできるのではないかと好奇心がわきました。
ファジングツールの開発にあたっては、次のことを目標としました。
- 最新のファジングフレームワークを使用する(AFL++ を選択しました)。
- ネットワークコードを変更して、ファイルからの入力を受け入れるようにする。
- テストケースごとに複数のリクエストを処理する(これについてはパート 2 で詳しく説明します)。
さらに、(自ら課した)HTTP ファジングツールの制約は次のとおりです。
- ソフトウェアのみのソリューションとする。
- Linux 上でネイティブに実行する。
上記の制約を念頭に置いていたので、ファジングツールにエミュレーションや専用ハードウェアを使用することは避けたいと考えました。
Linux への移植
AFL++ のような最新のファジングフレームワークを利用するには、µC/HTTP サーバーがネイティブの µC/OS カーネルではなく Linux 上で動作できるようにするコードを書く必要がありました。ただし、ファジングツールで具体的に何をテスト対象にするかを検討し、このプロトコル実装を Linux 上で実行できるようにフックの適切な挿入ポイントを決めなければなりませんでした。
以下は、µC/HTTP サーバーアプリケーションのソフトウェアコンポーネント構造を示す基本的なアーキテクチャ図です。青で表したものは、アプリケーションを Linux に移植するために変更を加えるコンポーネントです。オレンジで表したものは、ファジングツールの対象となるコードです。
µC/OS のモジュール設計は、このタイプのファジングに最適です。カーネル抽象化レイヤ(図の KAL)は、各ライブラリで使用される共通 API を提供します。これにより、ファジングツールでテストしたい RTOS 機能を模倣し、ファジングプロセスに関係のない機能については単に成功信号を返すカスタム KAL を簡単に作成できます。µC/OS には、µC/HTTP サーバーを Linux 上で実行できるようにする POSIX 互換の KAL がありますが、セットアップ全体を簡略化するために独自の KAL インターフェイスを作成することにしました。
KAL は、タスク管理、ロック、セマフォ、タイマー、メッセージキューのようなオペレーティングシステム コンポーネントの API ブループリントを提供します。µC/HTTP サーバーの場合、KAL が呼び出すのは KAL_TaskCreate 関数だけです。つまり、カスタム KAL インターフェイスではこの関数を実装するだけで済みました。さらに、テスト対象のタスクはメインの HTTP 処理ループなので、KAL_TaskCreate 関数内でこの関数自体を直接呼び出すことで、より複雑なタスク作成プロセスを回避するという方法を見つけました。以下にサンプルコードを示します。
ネットワーク機能の移植
ソケットの読み込み
前述の Linux への移植と同じようなことですが、µC/OS の TCP/IP プロトコルスイート内には、POSIX ソケットと似た機能を持つ NetSock というネットワーク抽象化があります。このファジングプロジェクトの目的は、基盤となる TCP/IP スタックを調べることではなく、あくまでも µC/HTTP サーバーコードに注目しています。ファジング入力が TCP/IP コードを通過することなく、直接 µC/HTTP サーバーコードに送られるようにするために、カスタム NetSock 実装を作成しました。
このファジングツールで最終的に目指しているのは、ファイルからネットワークデータを読み込むことですが、既存のツールを使用して実際のネットワークトラフィックをアプリケーションに送信すれば、テストとデバッグのプロセスが簡略化されます。開発中の µC/HTTP サーバーアプリケーションがブラウザで Web ページを読み込める程度に動作するようになれば、ファイルからプロトコルデータをリダイレクトするのはそれほど難しくないはずです。
そこで、NetSock を POSIX ソケットに移植することにしました。そのために必要だったのは、NetSock API 内で対応する POSIX 関数を呼び出し、適切な µC/OS エラーコードを返すことだけです。以下は、ソケットからデータを受信するコードの例です。
次に、各 NetSock_* 関数に対してこのプロセスを繰り返しました。正しい NetSock_* 関数署名を使用し、対応する POSIX ソケット関数を呼び出すと、NET_SOCK_RTN_CODE 型のエラーコードが返されました。
ファイルの読み込み
µC/HTTP サーバーアプリケーションが HTTP リクエストに適切に応答するようになったので、次のステップは、ファジングのために、NetSock コードが実際の POSIX ソケットではなくファイルから読み込まれるように設定することでした。ファイルから読み込む方法はいくつかあります。1 つは、libdesock というツールを使用することです。このツールで LD_PRELOAD を使用すると、Linux リンカーによって、他のライブラリより先に libdesock ライブラリが読み込まれます。ファイルのリダイレクトはこの方法で機能し、libdesock ライブラリが POSIX ソケット呼び出しを傍受して、stdin と stdout にリダイレクトします。
NetSock のコード変更についてはすでに十分な知識があったので、他にも簡単な方法が見つかりました。NetSock 内で recv を呼び出す代わりにファイルから読み込むというものです。そのためには、µC/HTTP サーバーアプリケーションがファイル名を引数として受け取り、そのファイルへのファイルポインタを curr_inputfd という名前のグローバル変数に格納するように実装します。以下は、ファイルからデータを「受信」するコードの例です。
注意しなければならないのは、上記のコードはファイルの最後(EOF)に達するとエラーを返すということです。このエラーは NetSock_RxData が処理ループ内で呼び出されるために発生し、この「ソケット」が閉じられていることを呼び出し元に示す指標となります。
アプリケーションをファジングする場合、ファジングツールが正常終了(通常のプログラム終了)とクラッシュを区別することが重要です。さらに、ファジング対象の入力を処理した後にプログラムが終了する必要があります。そこで、ファジングの目的で別のコード変更を行い、ファイルが読み込まれて処理されたらプログラムが終了するようにし、「通常」の終了となるようにしました。
この変更を行わないと、処理ループ内でアプリケーションが実行し続けるため、ファジングツールがこれを通常のプログラム終了ではなくハングとして誤認識してしまい、ハングの誤検出につながります。
この場所に exit を配置すると、接続を閉じる一般的な実行フロー中に exit が呼び出されます。また、前述のように、エラーコード NET_SOCK_ERR_CLOSED が NetSock_RxData から返されると、HTTPsConn_Close 関数がトリガーされ、その後、プログラムの実行が終了します。
メモリエラー
µC/HTTP サーバーアプリケーションを Linux 上でネイティブに実行できるように移植する主な利点は、アプリケーションを AddressSanitizer でコンパイルできることです。AddressSanitizer(ASAN)は GCC および Clang のコンパイラ機能であり、C/C++ のメモリエラーを検出できます。
ASAN をファジングと組み合わせて使用すれば、ソフトウェアの脆弱性を明らかにするための非常に価値のあるツールになります。ASAN を有効にした場合、メモリエラーが発生するとアプリケーションがクラッシュします。
解放後のメモリ使用、NULL ポインタの逆参照、バッファオーバーランなどは、通常であればテスト入力がプログラムをクラッシュさせない場合でも、即座にクラッシュを引き起こします。脆弱性の研究者にとって、ASAN によってプログラムがクラッシュするということは、「要チェック!この部分のコードに問題あり」という警告のようなものです。ファジングツールを実行するときに ASAN を使用しようとしましたが、開発した µC/HTTP サーバーアプリケーションのヒープメモリではすぐには機能しませんでした。これは、µC/OS が独自のヒープ実装を提供しているからです。
ASAN は glibc malloc で行われたヒープメモリ割り当てを追跡することで機能しますが、アプリケーションでは malloc をまったく使用していません。思いついた最も簡単な解決策は、アプリケーションが ASAN でコンパイルされていることを検出し、割り当てを glibc malloc にリダイレクトするプリプロセッサマクロを追加することでした。以下は、そのコードの例です。
µC/OS では、専用のヒープアロケータを使用して、開発者がヒープの位置と使用率に関する制約を定義できます。この機能は、メモリが限られていて、開発者がメモリ割り当てを細かく制御する必要があるシステムで特に役立ちます。ただし、私が開発したアプリケーションは Linux 上で動作するので、メモリ制限による制約はなく、標準の glibc malloc 関数にアクセスできます。Mem_DynPoolBlkGet 内で malloc を呼び出すだけで機能するのは、Mem_DynPoolBlkGet の呼び出し元が、返されるポインタの特定のメモリ位置を気にしないからです。重要なのは、使用可能で最小サイズ要件を満たすメモリチャンクをポインタが参照することです。コードにこの小さな調整を加えることで、アプリケーションの他の機能に影響を与えることなく、必要な Address Sanitizer(ASAN)機能を有効にできました。
脆弱性のハイライト
これまでに、µC/OS コードに戦略的な変更を加え、Linux 上でネイティブに実行でき、ファイルからの入力を受け入れることができるソフトウェアのみのファジングソリューションを作成しました。ここで説明したアプローチにより、5 件の脆弱性 TALOS-2023-1725(CVE-2023-24585)、TALOS-2023-1733(CVE-2023-27882)、TALOS-2023-1738(CVE-2023-28379)、TALOS-2023-1746(CVE-2023-31247)、TALOS-2023-1843(CVE-2023-45318)が発見され、その有効性が実証されています。
これらの脆弱性を公開したとき、µC/HTTP サーバーのコードベースの多くが複数の製品で共有されていることがわかりました。影響を受けるその他の製品には、Silicon Labs Gecko Platform と Weston Embedded Cesium NET があります。ここで説明しているすべての脆弱性は、シスコの情報開示ポリシーに従って製造元に報告されています。影響を受ける製品の脆弱性は、それぞれ対応する製造元によってパッチが適用されています。
これらの脆弱性に対するエクスプロイト試行は Snort ルール(119:201、119:281、1:12685、1:39908)で検出できます。今後、脆弱性に関する新たな情報が追加されるまでの間は、ルールが追加されたり、現行のルールが変更されたりする場合がありますのでご注意ください。最新のルールの詳細については、Cisco Secure Firewall または Snort.org を参照してください。
このシリーズの次回の記事では、テストケースごとに複数のリクエストを処理する複雑さについて説明します。この機能強化により、さらに多くの脆弱性が明らかになる可能性があります。
本稿は 2024 年 08 月 28 日にTalos Group のブログに投稿された「Fuzzing µC/OS protocol stacks, Part 1: HTTP server fuzzing」の抄訳です。