TrustZoneの深層にせまる:組込みシステムのセキュリティ強化

組込み機器でもネットワークにつながることが増えてきています。無線や有線で繋がる事によって、単に通信するだけでなくソフト自体を書き換え(OTAなど)などの動きも盛んです。ネットワークにつながることで、通信による負荷をあげてシステムダウンをさせたり、機器から機密情報(パスワード含む)、ソフトウェアを書き換えてシステム自体を乗っ取るなど様々な悪意のある行為にさらされる可能性があります。ネットワーク化が進む組み込み機器でもセキュリティが重要になってきました。そのためArmv8-Mでは、以下の2点が追加されました。

  • TrustZoneによるセキュリティ向上
  • スタック保護

それぞれについて見ていきます。

TrustZoneによるセキュリティ向上

TrustZoneArmv8-Mの目玉機能です。Armv7-Mのソフトウェアを再利用性も可能にしながらセキュリティの強化をしたアーキテクチャとなります。

Secure/Non-Secureの2つの状態を持ちます。SecureNon-Secureは基本的には分離されており、2つの状態ごとにそれぞれプログラムを持ちます。

 

TrustZoneでの実装イメージ

もちろん、この2つのプログラムは連携するのですが、信頼性の高いプログラムを実行するSecure状態と、そうではないプログラムを実行するNon-Secure状態の2つで構成されます。EWARMではこの2つのプログラムを作成するためのオプションが有り、それぞれのプログラムを作るプロジェクトオプションを以下のように設定します。

TrustZoneのメモリ処理について

TrustZoneを使ったメモリ空間は特徴があります。まず、FLASH, RAMは2つのアドレスからアクセスできるようになっています(STM32L552ZEを例に記載)。Armv8-Mでは、メモリ領域セキュリティ属性が追加され、ベンダが設定したデフォルト属性がIDAU設定されており、さらにSAUをユーザが設定する事ができます。IDAU+SAUの値で最終的なメモリ属性が指定されます。図のようにモード毎にアクセスできるアドレス領域を指定する事が出来ます。これによりNon-Secure状態からアクセスできない領域などの指定が可能となり、Secureのプログラムデータを保護する事が可能です。最終的にはSecure領域、Non-Secure領域、NSCNon-secure callable)領域の3つに分類されます。

TrustZoneの実行面での特徴は、SecureNon-Secureの2つの状態を切り替えながら動作することです。Secure→NonSecureへの移行、NonSecure→Secureへの移行をどうするのかを見ていきます。

Secure⇒NonSecureの関数呼び出しと戻り

Cortex-Mの関数呼び出しは、基本BLXで呼び出し、BXで関数呼び出しから戻るというスタイルです(ただ、この1つだけでなく、いくつかバリエーションがあります)。それに対して、SecureからNon-Secureの関数を呼び出すには、BLXNS命令にてジャンプすることで合わせて状態も遷移し、Non-SecureからSecureに戻るためには BXで戻ります。基本的にNon-SecureなプロジェクトはTrustZoneを意識する必要がない、というのがポイントです。

BLX命令はレジスタで指定したアドレスにジャンプし戻りアドレスをLRに設定します。これは関数呼び出しそのものです。それに対して、BLXNS命令では、レジスタで指定したアドレスにジャンプ、LR0xFEFFFFFFに設定し、スタックに戻りアドレスを積んで、SecureからNon-Secure状態に切り替えます。通常の関数ではLRに戻りアドレスを設定しますが、SecureNon-Secureの場合にはスタックに戻りアドレスを積んでいるところがポイントです。この仕組みは割込みに似た仕組みとなりますが、LRに設定される値が異なります。

Non-SecureからSecureに戻るにはBX命令で戻ります。Non-SecureはプログラムはTrustZoneで追加した命令を使えません。マイコン側ではLRの値が0xFEFFFFFFの時に、BX命令が実行されると、スタック上の戻りアドレスにジャンプし、Secure状態に戻ります。このBX命令で通常の関数呼び出しの場合にはLRに戻りアドレスが設定されているので、PCをそのアドレスに設定します。             

これまでの動作を解説して来ましたが、EWARMでの実際のコードを見てみましょう。

ここでは、Secure側のプログラムからNon-Secureで実装されているns_funcを呼び出します。そのためにSecure側プログラムでは、__cmse_nonsecure_callというキーワードを使って関数ポインタ型を定義しています。これにより、この関数ポインタを通して呼び出される関数はNon-Secure側のものとなります。cmse_nsfptr_createを使って呼び出す関数のアドレスを関数ポインタに設定します。通常Cortex-Mではジャンプ先を示す時には最下位ビット(LSB)1にしますが(Thumb命令の特徴)、TrustZone関係の場合はLSB=0にする必要があります。そのためcmse_nsfptr_createではLSBをクリアしています。そしてfp_ns();にて関数呼び出しをしています。

関数fp_nsを呼ぶと、BLXNS命令を使ったコードが生成されます。この際に生成されたコードが大切なので見ておきましょう。Secure⇒Non-Secureに遷移する時に、R0R12CPSRの値を意味ない値に変更するためのコードをコンパイラが生成しています。Non-Secure側にSecureのデータ値が漏れないようにしています。              

こうしたことから、TrustZoneを使ってプログラムする際には、アセンブラで書くとこうした点が忘れられたり、不十分だったりする可能性があるため、コンパイラの機能を使うことがおすすめです。以下の例で退避するレジスタは状況によって変化しますので、ご注意ください。

Non-Secure⇒Secureの呼び出しと戻り

NonSecure⇒Secureへの状態変更は決められた手続きを実行する必要があります。以下の4つの手順が必要となります。

  1. Non-Secureモードでのプログラムが、NSC領域にジャンプする
  2. NSC領域に配置されたSG命令を実行し、Secureモードに切り替える
  3. Secureの関数を呼び出し、SECURE側での処理を実施
  4. Secure側の処理がおわると、BXNS命令を呼び出し、Non-Secureモードのプログラムの実行に戻る。

ここでのポイントは、NSC領域のSG命令を実行しないとSecureに遷移できないということです。そのため、Non-Secure領域にSG命令を配置し実行してもSecureに遷移しない点が重要です。攻撃により簡単にSecure状態に移行できないということです。

 

これも実際のコードで見ると簡単です。Secure側のプロジェクトでは、Non-Secureから呼び出される関数を__cmse_nonsecure_entryで宣言します。Non-Secure側のプロジェクトは、特にTrustZoneプログラムを変更する必要はなく、単純に関数呼び出しをするだけです。

プロジェクト設定をしておくことで、Secure側のプロジェクトは、関数の実体と、NSC領域に配置するSG命令+ジャンプ命令を生成します。下の図の右側がNon-Secureプログラムで1にてNon-SecureのコードからNSC領域のSG命令ジャンプをします。SG命令を実行する前はNon-Secure状態です。2の命令を実行するとSecure状態になります。そして3にて実行する関数にジャンプをし、処理が終了するとBXNS命令でNon-Secure状態になりNon-Secureプログラムの実行を再開します。

しかし、TrustZoneの機能を理解しただけでは、セキュリティの高いシステムを作ることは出来ません。TrustZoneはセキュリティを実装する手段を提供しているだけなので、プログラムの違法コピー、システム内のパスワードや重要情報を漏洩、ソフトの改ざんなどさまざまな対応すべき課題があり、それぞれに応じた対策を実装する必要があります。また、近年では ISO/SAE 21434ISO 24089 IEC 62443などの規格も出揃い、よりセキュリティが重要な要素になっています。

 

IARでは、EWARMの開発支援でのサポートに加えて、セキュリティソリューションとしてIAR Embedded Trust提供しています。これによりTrustZoneをどのように使うか、鍵をどこに置くか、などについてユーザで考える必要がなく、セキュリティを実現することができます。

スタック保護

Armv8-MではPSPLIM, MSPLIMというレジスタが追加されました(Cortex-M23ではセキュリティ拡張が実装されないとサポートされない)。ここにスタックの上限(0x0側)を設定すると、その上限を超えた場合に例外を発生することができます。セキュリティという観点ではスタックオーバーフローを利用して、システムを攻撃する可能性などがあり、こうした機能が増えることでセキュリティを強化しています。

使い方は簡単で、MSPLIMPSPLIMにスタックの上限値(0x0側)を設定するだけです。

スタック上限を超えた場合には、UsageFault例外が発生します。そのためSystem Handler Control and State RegisterSHCSR)の18ビット目USGFAULTENAの設定により少し動作が変わります。このUSGFAULTENUsageFaultの有効無効を設定するビットとなります。

USGFAULTENA1に設定しているとUsageFaultが有効のため、UsageFaultハンドラが呼びされます。USGFAULTENAを0に設定しているとUsageFaultは無効のため、HardFaultハンドラが呼び出されます。

UsageFaultの場合はUsageFault Status Register(UFSR)に要因が示されます。4ビット目がSTKOFとなりスタックオーバーフローフラグとなります。このビットはArmv7-Mにはないので差分を調べてみると勉強になると思います。

組込みセキュリティはますます重要に

ISO/SAE 21434、ISO 24089、IEC 62443などのセキュリティ規格は、組込みの世界でますます重要性を増しています。 Armv8-MのTrustZoneとスタック保護機能は、安全で信頼性の高い組込みシステムを構築する上で不可欠です。

 

しかし、TrustZoneを搭載しているだけでは十分ではありません。データ盗難、パスワード漏洩、ソフトウェア改ざんなどの脅威から保護するには、より広範なセキュリティ戦略も重要です。

IAR Workbench bench for ARMでは、TrustZoneを使って開発をするための拡張がされてるため、簡単にセキュリティ機能を利用することが出来ます。IAR Embedded Trustに搭載されているセキュアブートマネージャーは、TrustZoneの実装の詳細をすべて把握していなくてもセキュリティを簡素化します。

次世代のマイクロコントローラを選択する際には、性能だけでなく、セキュアなシステムを一から構築するための適切なツールが装備されていることを、ぜひ確認してみてください。

 

本シリーズのそのほかの記事はこちら
Cortex-M23およびCortex-M33におけるArmv8-M アーキテクチャ徹底解説
Armv8-Mでパフォーマンスと開発効率を最大化

赤星
赤星 博輝
IARシニア・フィールド・アプリケーショ ン・エンジニア

九州大学で情報工学の学士号(1991年)、修士号(1993 年)、博士号(1996年)など、数多くの学位を取得しています。 研究対象は主にCPUアーキテクチャとコンパイラですが、組み込みソフトウェア開発での経験があります。これまで、20年以上にわたり自動車業界の安全性と機能性に焦点を当てた幅広いプロジェクトで活動をしてきました。