組込みシステムの安全性とセキュリティのリスクを軽減する

オープンソースソフトウェア

MCU ベースのアプリケーションにおけるメモリ破損を検出して軽減するための新しいメカニズムを導入します。

リアルタイム オペレーティング システム (RTOS) レベルで作業する組込みソフトウェア開発者は、メモリ破損の問題は発生しやすいが検出が難しく、アプリケーションの安全性とセキュリティに壊滅的な影響を及ぼすことが多いことを知っています。このため、これらの問題の防止は、産業業界、テストツールベンダー、RTOS ソリューションプロバイダーの間で人気のテーマとなっています。それにもかかわらず、マイクロコントローラー ユニット (MCU) デバイスはより高度なプログラミングや RTOS 技術をサポートできないため、メモリ破損を軽減するには依然として大きな制約が存在します。

世界中の何十億ものアプリケーションの中核となる MCU は、安全性とセキュリティに特有の課題を抱えています。メモリ管理ユニット (MMU) やメモリ保護ユニット (MPU) がなく、コードにメモリ保護機能を組み込むための容量がほとんどない MCU は、組込み開発者にシステムの堅牢性を確保するための選択肢をほとんど提供しません。

MCU の導入が増え続ける中、より多くシステムとの接続がサポートされるようになるにつれ、開発者は、これらのハードリアルタイムシステムの厳格な機能要件とパフォーマンス要件を損なうことなく、メモリ破損の問題から保護するためのより良い方法を必要としています。

MCU ベースのアプリケーションの安全性とセキュリティにおける課題

プロセッサのワークロードとメモリのフットプリントを最小限に抑えるために、MCU ベースのアプリケーションは、すべてのスレッドでグローバルに共有される単一のアドレス空間内で実行される傾向があります。メモリを異なるアドレス空間に分割する上位レベルの OS (組込み Linux など) 上で実行されるアプリケーションとは異なり、MCU アプリケーションはメモリの全範囲にアクセスできます。このモノリシックなアプローチでは、開発者は、アプリケーション機能によるセグメント外のメモリの破損を防ぐ独自の方法を見つけるという負担がかかります。

破損は、有効なアドレスから疑わしいアドレスへのポインタの変更など、予期しないまたは望ましくない手段によってメモリ内のデータが変更されたときに発生します。たとえば、関数ポインタが破損すると、プログラムの実行が無効なメモリ位置にジャンプし、システム障害が発生する可能性があります。ハッカーがこの脆弱性の存在を疑う場合、保護されていないポインタを悪用して、コード インジェクションと呼ばれる手法を通じて導入した悪意のあるコードを誤って実行する可能性があります。

関数ポインタの破損

表 1: 関数ポインタ検証の例のメモリ割り当て (出典: PX5 RTOS)

メモリ破損を防ぐには、先見性と知識の両方が必要です。開発者は、問題がいつ発生するかを認識し (複数のスレッドが同じメモリ位置にアクセスするアプリケーションでは必ずしも容易ではありません)、問題の発生確率を最小限に抑える方法を知る必要があります。メモリ保護が組み込まれていないデバイスの一般的な修復戦略をいくつか示します。

  • NULL ポインタは、使用時に参照が有効なメモリ位置を指していることを確認するためにチェックします。
  • チェックサムまたは CRC を使用して、安全でない可能性のあるメモリ アクセスを検出して処理します。
  • 値を、異なるメモリ場所に保存されているデータと比較して、それらが一致するかどうかを確認します。

これらの手法はデバイスリソースを消費し、開発者がそれらを使用する場所を覚えていることに依存します。後者の 2 つは、計画、アーキテクチャ、テストにかなりの労力を必要とします。

PX5 RTOS は、要求の厳しい MCU ベースのアプリケーション向けに設計されており、開発者とデバイスのオーバーヘッドを最小限に抑えるメモリ保護への組込みアプローチを提供します。このアプローチは、Pointer Data Verification (PDV) と呼ばれます。

PDV によるメモリ破損の軽減

PDV は、PX5 RTOS に固有の無駄のない堅牢なメモリアドレス検証方法です。PDV は、MMU または MPU にアクセスできないリソースに制約のある MCU アプリケーション向けに特別に設計されており、アプリケーションが予期しない不正なアクセスを回避できるように、メモリ構造に固有の検証コードを作成して保存するソフトウェア専用の技術です。

開発者が PDV をオンにすると、PX5 RTOS は機密データの場所に値をロードする際に検証コードを自動的に作成します。アプリケーションが機密データを使用する前に、RTOS は検証コードを再度生成し、保存されている値と比較します。これら2つのコードが一致しない場合、PX5 RTOS は、開発者が指定したアクションを実行する中央エラー処理関数を呼び出します。

開発者は、検証コードを生成するための式を定義することも、PX5 RTOS が提供するデフォルトのメカニズムを使用することもできます。デフォルトの検証コードは、RTOS に渡される秘密のランタイム ID (真の乱数発生器からの結果など)、機密データの値、および生成されたコードを保存するアドレスの組み合わせです。デフォルトの式は次のようになります。 検証コード = ((データ値) + (ストア コードへのアドレス) + (シークレット)) ^ (シークレット)

開発者による PDV の使用方法

開発者が PDV を使用して機密データ構造を保護する方法の1つの例は、Function Pointer Verification (FPV) です。PX5 RTOS を使用してアプリケーションを構築する場合、開発者は PX5_FUNCTION_POINTER_VERIFY_ENABLE フラグを使用して、使用前にすべての関数ポインタを確実に検証できます。PX5 RTOS でサポートされている POSIX スレッド (pthreads) API を使用してアプリケーションの開始ルーチン関数ポインタを作成する例を見てみましょう。

コンパイラが表 1 で指定されているようにメモリを割り当てたとします。さらに、entry_routine 関数ポインタの計算された検証コードが 0x9D919C7D であるとします。図 1 は、生のメモリ形式とデータ監視の両方で、スレッド作成後のスレッド制御ブロックを示しています。

制御ブロック関数ポインタ

図 1: 関数ポインタ検証のスレッド制御ブロックの例 (出典: PX5 RTOS)

start_routine() 関数ポインタが実行時に使用されると、PX5 RTOS は検証コードを再計算し、保存されている検証コード 0x9D919C7D と比較します。新しく計算されたコードが保存されたコードと一致する場合、関数ポインタは有効であるとみなされ、関数 start_routine() が呼び出されます。コードが一致しない場合は、関数ポインタまたは保存された検証コードのいずれかでメモリ破損が発生する可能性が高いため、PX5 RTOS は中央エラー処理関数を呼び出します。

このアプローチでは、プログラムの実行をリモートから変更しようとするハッカーは、挿入されたコードが一致する検証コードを生成できないため失敗します。

メモリ破損を根本から保護する

PDV は、アプリケーションのパフォーマンスやリソースの予算を犠牲にすることなく、メモリ破損の問題を確実に検出して軽減する機能を開発者に提供する特許出願中のテクノロジです。PX5 RTOS 自体は超小型 (最小限の使用で 1 KB 未満)、超ポータブル (完全準拠の pthreads API を搭載) で、厳密にテストされています (リリースごとに 100% C ステートメントと分岐決定カバレッジ)。堅牢な決定性、安全性、セキュリティを必要とするシステムにとって理想的な基盤です。

PDV はメモリ保護ハードウェアのない MCU 環境向けに設計されていますが、MPU および/または MMU 機能を備えたより堅牢なプロセッサ向けにコードの安全性とセキュリティの実践を補完することもできます。

PX5製品情報はこちら