2019/10/14

[arm]Armv8-MのTrustZone

nRF9160で気になっていたのが、TrustZoneだ。
Cortex-AのTrustZoneを知らないので比較できないのだが、この記事によるとGCCから扱えるようだ。

https://www.lobaro.com/using-the-armv8-m-trustzone-with-gcc/


Introduction & Motivation

Cortex-M33プロセッサにはARM TrustZoneと呼ばれる機能がある。
この記事では、GNU ARM GCC compilerとCMSE(Cortex M Security Extensions)を使ってTrustZoneついての私見を述べる。

これはARMv8-MならnRF9160じゃなくてもいいし、GCC以外のコンパイラでもいい。

 

Secure & Non Secure CPU Operationg States

今まではThreadモード(通常モード)とHandlerモード(割込みモード)だけだったが、ARMv8-Mになるとsecure / non-secureという属性が加わる。

電源を入れたときはsecureモードで始まる。このときはメモリも周辺機能も全部使える。これは今までのM3やM4などと同じ。

TrustZoneの考え方は、セキュリティ関連のコードやデータをデバイス内の小さな特定領域に隠すということだ。その領域は"secure"と呼ばれ、そうじゃないところは"non-secure"と呼ばれる。メモリ、割込み、周辺機器をsecure/non-secureに分割するのはユーザが決める。電源ON時のコードはその分割をする処理から始めるのが普通だ。nRF9160のコード例(Secure Partition Managerと呼ぶらしい)では、FLASHの256KBをsecureに、768KBをnon-secureに割り当てている。

CPUは実行関係のレジスタ(スタックポインタとSystem Control Block)として2つのバンク(secure/non-secure)を持つ。今のCPU動作がsecureかnon-secureかというのは、どっちのバンクを使っているかということと同じ意味である。バンクのメカニズムはハードウェアで2回実装されている一部の周辺機器(SysTickなど)でも同様です【私:2回(twice)って何??】。secureなプログラムは常にどちら側にもアクセスできる。non-secureなスタックポインタとnon-secureなベクタアドレスの配置を設定した後、secureなブートコードはnon-secureなベクタテーブルの関数ポインタを使用する。そして、CPUはnon-secureなファームウェアとして動作し始める。

non-secureファームウェアは、secureファームウェアの存在を認識してはならない。TrustZoneがない時代と同じように普通に開発して、普通にnon-secureなメモリ領域とリンクするようにする。CPUがnon-secureに動き始めると、security exceptionが発生しない限りsecureなコード、データ、周辺機器にに直接アクセスできなくなる。non-secureコードからsecureな機能にアクセスする唯一の正当な方法は、secure側が提供することになる特別なゲートウェイ/エントリー/veneer機能を使うことである。では、ARM GCCとCMSE(Cortex M Security-Extensions)を使ってやってみよう。

 

Non-secure callable(NSC) memory regions and the secure gateway(SG) ASM instruction

non-secureからsecureな機能を呼び出せるようにするため、secureファームウェアのコードに"Non-secure callable(NSC)"といういくつかの小さなFLASH領域を設定しなくてはならない。nRF9160ではSPU(System Protection Unit。PDFのp.257参照)が担当している。

secureメモリの一部をNSCとして指定することにより、最初のOPコードとしてアセンブラの"SG(Secure Gateway)"命令を呼び出すことで、non-secureから呼び出せないという制限を緩和できる。SG命令を実行すると、プログラムはより制限されたsecureメモリ内にある=NSCではないメモリにあるsecure機能実装に分岐する。このようにしてnon-secureコードからsecure機能を呼び出すことで、まずNSCにジャンプし、続けてsecureメモリ内にある実際の機能にジャンプする。こういう過程を経ることにより、secureメモリへのアクセスは残りのsecureメモリから切り離される。詳しいことはこちらを読め:Reasons for introducing the NSC regions

 

Defining non-secure callable functions in secure firmware

幸いなことに、non-secureファームウェアからsecure機能を呼び出す過程はARMv8-Mコンパイラによってプログラマからは隠されている。non-secureファームウェアから呼び出すsecure機能のエントリーポイントはsecureファームウェアの定義をした後でcmse_nonsecure_entry属性をつける。

//some c file of secure firmware project defining veneer gateway functions
(ソース例は原文を見よう)

GCCでコンパイルするときにはオプションとして"-mcmse"がいる。

// GCC COMPILER flags used during secure side building
arm-none-eabi-gcc -o secureFirmware.elf -mcmse [...]

関数本体は通常secureメモリの.textセクションに配置される。が、SGと分岐する命令は.gnu.sgstubsセクション(GCCの場合。GCC以外は違う名前かもしれない)。

NSC領域の小さなredirect機能は、SGを実行して分岐する処理しかやらないので、"veneer"機能や"SG stubs"などと呼ばれる【私:blogでは"SG stups"と書いてあったけど、typoよね? それと、ここまでfunctionを「機能」と書いてきたけど、C言語の関数という意味で使われたりもする? redirect functionってなんだろう? non-secureからsecureに遷移するってこと?】。secureファームウェアのリンカスクリプトファイルは、non-secureファームウェアにジャンプする前にsecureブートプロセスの一部のNSCとして宣言するveneerコードをsecure領域に配置しなくてはならない。

// Linkerscript Section for TrustZone Secure Gateway veneers
(リンカスクリプトは原文を見よう)

【私:何が言いたいのかわからなかった。リンカスクリプトの見方が分かってない。】

 

Calling secure functions from non-secure firmware (NS->S)

通常、secureファームウェアはnon-secureファームウェアイメージから独立して開発&FLASH書込みされる。secureファームウェアの機能を使うために、secure側の関数を呼び出せるように書かれたヘッダファイル(.h)はnon-secureソースファイル側でincludeする。加えて、non-secureファームウェアはsecureメモリのNSC部分の前にveneerエントリーポイントスタブがどこにあるかを知っていないといけない【私:「NSC部分の前に」はbeforeだから時間的に前。FLASHのsecureとnon-secureの設定を行う前に決定していないといけない、という意味か?】。これは特殊なオブジェクトファイル(CMSE_importLib.o)とアドレス情報を必要としているnon-secureファームウェアをリンクすることで解決する【私:done、は、何とかしてくれる、くらいの意味だろうか?】。このオブジェクトファイルは以下のcmseリンカオプションを使用してsecureファームウェアがリンクされる間に生成されなくてはならない【私:CMSE_importLib.oはここで生成されているのだろうか?】。

// GCC LINKER flags used during SECURE side linking
arm-none-eabi-gcc -Xlinker --cmse-implib -Xlinker --out-implib=CMSE_importLib.o -Xlinker --sort-section=alignment [...]

オプション"-in-implib=CMSE_importLib.o"は、過去バージョン(TrustZone未対応)で既に定義された同じメモリに展開されるオブジェクトファイルを新しいバージョン(TrustZone対応)でも維持できるようにする【私:-in-implibではなく-out-implibの間違いでは?】。こうすることで(過去バージョンで作成済みのimport libにリンクされている可能性がある)non-secureイメージの更新をせずにsecureイメージに追加できるようになる。

.hファイルを使用してCMSE_importLib.oオブジェクトをリンクするために、non-secureファームウェアはControlCriticalIO()関数を呼び出す【私:the example aboveは、たぶん「原文を見よう」のコードだ。】。

// GCC LINKER flags used during non-secure side linking
arm-none-eabi-gcc -o non-secureFirmware.elf CMSE_importLib.o [...]

 

Calling non-secure (callback) functions from secure firmware (S->NS)

どのようにしてTrustZoneが2つの隔離されたファームウェアをgatewayやveneer機能で接続するのかを見てみよう。これはsecureファームウェアがnon-secureファームウェアから呼び出される機能を許可しているからだ。

もう1つの方向、すなわちsecureファームウェアからnon-secure関数を呼び出す場合、なんらかの方法でsecureコードにnon-secure関数のアドレスが与えられたならば、実行することができる。通常、これはsecure gateway functionが引数にnon-secureコールバックポインタを与えることで行われる。

(サンプルは原文を見よう)

"nonsecure_call()"属性は、潜在的なセキュリティリスクを割けるためにnon-secureコードにジャンプする前にバンクじゃないgeneral purposeレジスタをクリアするようsecureコンパイラに指示するために必要だ。そしてまた、ブランチアセンブラ命令の関数ポインタアドレスLSBをクリアする指示にも使われる。こういうことを行って、CPUはsecure状態からnon-secure状態に戻ってくる。

non-secureコールバックはvolatile値のように扱わねばならないので、non-secure割込みによって変更される可能性があるため、関数ポインタは"cb"にコピーが必要となる【私:cbはサンプルコードの中にあるfuncptr_ns型の変数か】。コールバックを実行する前に、ポインタがnon-secureメモリのみを挿しているのかチェックしなくてはならない。secureメモリを挿しているなら、それはセキュリティリスクとなりうる【私:ポインタ変数が4byteだとして、それが全部non-secure側にあるかどうかをチェックするのだろうか?】。

 

Hints and further reading

secure側のリンカスクリプトはveneer関数のセクションを考慮し、non-secure側のリンカスクリプトはsecureファームウェアのメモリ領域を反映しなくてはならない、ということを覚えておこう。archiveファイル(*.a)がnon-secureの呼び出し可能な関数を含んでいるなら、secureファームウェアのリンカオプションとして"whole-archive"を付けなくてはならない。この方法でのみCMSE import libが必要とするveneer関数に含めることができる。

 


けっこうがんばって読んでみたのだが、どうだろうか?
というか・・・私やっぱり英語力ないな。。。
ただ、この人のはGoogle翻訳などできれいに和訳されやすかった。がんばったのは、Google翻訳に通す前に自分でやってみたということについてだ。自信がないときはその後でGoogle翻訳して「ああやっぱり間違ってた」となるのだがね。

 

 

さて、整理しよう。

【NSC】
Non-Secure Callableの略。
non-secure状態からsecure状態に遷移するときに使うsecure側の領域。NSC memory regionsやNSC regionなどという使い方。

【SG】
Secure Gatewayの略。
non-secure状態からsecure状態に遷移したいときに使うアセンブラ命令。

 

まず、Defining non-secure callable functions in secure firmwareのところ。
ControlCriticalIO()という関数をcmse_nonsecure_entry属性つきで作る。
これはnon-secureファームウェアから呼び出されることになるのだが、その前にSGオペコードを呼び出したりなどをやってくれるのだろう。
これをsecureFirmware.elfという名前で生成している。-mcmseオプション付きだとsecure用になるのか。

 

次に出てきたのはリンカスクリプトで、.gnu.sgstubsが載っている。
これは自作が必要なのかもしれない。

 

次はCalling secure functions from non-secure firmwareのところ。
--Xlinkerを使ってリンカに指示している。オプションは--cmse-implib, --out-implib, --sort-section。
--out-implibはファイル名を付けているので、これが生成されるのだろう。

そして最後にCMSE_importLib.oと一緒にリンクしてnon-secureFirmware.elfを生成している。

 

最後の話で、callback_fnをcbにコピーするところがよくわからなかった。
C言語だから、引数は値渡しだよな? ポインタだから、アドレス値を渡しているはずだ。
じゃあ、コピーしなくてもいいんじゃないのかと思ったが、レジスタじゃなくてスタックで渡された場合だろうか。
うーん、答えが出ない。。

 

あと、secureとnon-secureにメモリのパーティションを分ける話があったが、FLASHはいいとして、SRAMの方はどうなってるのだろうか?
nRF9160のSPMを読むと、SRAMも分けるようだ。分けるというか、強制的に分けられるというべきか。だからSRAMについての記述がないのか。

 

nRFのFLASHって、自分で書き換えができたんだっけ?
OTAがあるのは覚えているが、BondingなんかはAPIを使ってしかやってないので、今ひとつ・・・いや全然記憶にない。


むー、もっと調査が必要か。

しかしまあ、しばらく触っていないうちにずいぶん状況が変わってきたな。
特にセキュリティ周り。
nRF51822だとBLEで手一杯だったので考慮しなかったが、nRF9160はTrustZoneがいるくらいのレベルが要求されているのだろう。まあ、nRF9160はBLEじゃなくてインターネット接続と直接接続される前提になっているからかもしれないが。

今までインターネットとは距離を置くことでセキュリティのことは放置してきたのだが、そういう時代ではなくなったのだな。。。

0 件のコメント:

コメントを投稿

コメントありがとうございます。
スパムかもしれない、と私が思ったら、
申し訳ないですが勝手に削除することもあります。