⚠️

Cortex-M用TrustZoneの脆弱性「Stack Sealing Vulnerability」について解説する

2023/12/30に公開

はじめに

Cortex-MのTrustZoneについて調べている中で中々、興味深い脆弱性があったので解説します。

想定読者のレベル

  • Cortex-Mで開発をしたことがある
  • Cortex-M用TrustZoneについて知っている

という人を想定してます。

もしCortex-M用のTrustZoneについて詳しく知らない場合はお金はかかりますが詳しく書いておりおすすめです。

https://interface.cqpub.co.jp/magazine/202312/

概要

Cortex-M用TrustZoneはリアルタイム性を考慮しながらSecureな部分がNon-Secureな部分から論理的に分離されており、秘匿したいデータが露出しない仕組みとなってます。メモリに関してはAHB(AHB5)で監視する機構が入っておりNon-Secure側にとってSecure側は存在しない領域となっております。スタック自体もSecure側とNon-Secure側で分離されております。レジスタもSecure側からNon-Secure側に遷移する際にコンパイラにより自動的に削除する命令が生成されてクリアされてます。

ただしCortex-Mは元々、リアルタイム性を出すためにトリッキーな設計となっておりさらにTrustZone自体を搭載した上でリアルタイム性を維持するために輪をかけてトリッキーな設計となっています。そこを狙ったのがこの脆弱性です。

CVEは2020-16273でCVSSは7台とそこそこ高い値となってます。

https://debricked.com/vulnerability-database/vulnerability/CVE-2020-16273

詳細なメカニズム

EXC_RETURN

多くのCortex-Mの文献ではこの部分はそれなりの分量を割いて解説されます。このトリッキーな設計のCortex-Mのミソといっても過言ではありません。

EXC_RETURN自体は知っている前提で解説しますがこの中のS bitでSecureとNon-Secureのどちらの状態から戻ってきたか、Mode bitでハンドラモードかスレッドモードかをCPUに知らせています。ここが今回の肝です。


https://developer.arm.com/documentation/ddi0553/latest

Non-Secure function callと割り込み処理時の通常のフロー

通常、Secure側からNon-Secureな関数を呼び出した場合はPartial xPSRと戻りアドレスがSecure StackにPushされます。Non-Secure側から戻る際、つまりFNC_RETURNで戻る際はそのPartial xPSRと戻りアドレスがPopされその値を元にCheckが走り合格すればSecure側の元の処理に戻るという流れです。

一方、Secure側のコードを実行中にNon-SecureなIRQが走った際はSecure StackにIntegrity SignatureがPushされます。Non-Secureな割り込みハンドラ内の処理が終わり戻ってきた際、つまりEXC_RETURNで戻る際はその値をCheckするという流れになります。

つまりFNC_RETURNで戻った際はPartial xPSRと戻りアドレス、EXC_RETURNで戻った際はIntegrity Signatureがあることを期待しているわけです。

簡単な攻撃時の挙動

そういう特性のため、通常の攻撃は検知することが可能です。
Example1、2はともにLRを書き換えようとしてますが、上記の仕組みによりチェックを合格することができず例外に落ちるオチとなってます。

図がわかりにくいですがこの場合に、Non-Secureへ遷移する前と後のスタックは同じものを使っているのだろうと思います。

問題となるケース

ある程度は堅牢にできてますが抜け穴があります。

例えばSecureな特権ソフトウェアが新しいSecureなスレッドのためにProcess Stackを新規作成した場合です。

  1. Secure側のスレッドモード(Process Stack)でNon-Secureな関数を呼び出しNon-Secureなスレッドモードに遷移する。
  2. そのままNon-Secureなハンドラモードに移行する。
  3. Non-Secureなハンドラモードで偽のFNC_RETURNをLRに書き込み無理やりSecureなハンドラモードに遷移する。

という攻撃を食らうと

  1. 全く関係ないSecure側のMain Stackは身構えておらず意図しないコード実行をする可能性がある。

ということになります。
もしかしたら例外に落ちる可能性もありますが、落ちずに意図しないコードを実行する可能性もあります。

対策

その対策としてはARMが推奨しているのがStack sealing valueというものをスタックに埋め込んでおくことです。

この値は0xFEF5EDA5となっており、eXecure Never(XN)領域のアドレスです。また、Integrity Signatureの値とも適合しません。そのため「問題となるケース」の4.において読み込まれた場合も確実に例外に落ちる設計となってます。

ARMとしてはNon-Secure側への初回遷移時以前やSecure側のスレッド用のスタック生成時等にこの値を埋め込むことを期待してます。

一応、CMSIS-Coreでも対応されてます。

https://www.keil.com/pack/doc/CMSIS/Core/html/using_TrustZone_pg.html#RTOS_TrustZone_stacksealing

参考文献

Discussion