Chapter 07
分離 — TrustZone・MPU・PMP・TEE
この章がなぜ必要なのか——「バグはある」という前提で設計するため.
セキュアブートは「不正なコードを実行させない」ための仕組みだった。 だが 04 章で見たとおり、正規に署名された脆弱なコードは通ってしまう。
ネットワークスタック、BLE スタック、JSON パーサ、画像デコーダ—— これらに脆弱性が見つかるのは時間の問題である。
だから 「脆弱性を突かれても、鍵は取られない」 という構造を作る。 それが分離である。
1. 分離の考え方
鍵を「使える」が「読めない」——これが核心である.
Non-secure 側のアプリは、Secure 側に「この平文を署名して」と依頼できる。 依頼はできるが、鍵そのものを読むことはできない。
だから脆弱性を突かれても、攻撃者にできるのは 「その機器がオンラインな間、署名を作らせる」ことまでで、 鍵を持ち去って別の場所で使うことはできない。
これはとても大きな違いである。攻撃の規模が「1 台・一時的」に閉じ込められる。
2. 分離の 3 つのレベル
| レベル | 何を分離するか | 実現手段 |
|---|---|---|
| レベル 1: 特権分離 | 特権/非特権 | MPU / MMU(従来からある) |
| レベル 2: セキュリティ状態の分離 | Secure / Non-secure | TrustZone、RISC-V PMP+ |
| レベル 3: 物理的な分離 | 別のプロセッサ/別のチップ | セキュリティサブシステム、セキュアエレメント |
強度は下に行くほど高く、コストも高い。
TrustZone のメモリ分割を見る
領域の属性を変えて、どのアクセスが許され、どれが Fault になるかを確かめられる。
3. Arm TrustZone-M(Armv8-M)
Cortex-M23 / M33 / M35P / M55 / M85 が持つ機構である。
基本の構造
CPU は 2 つの「セキュリティ状態」を持つ。
| 状態 | 実行できるもの | メモリアクセス |
|---|---|---|
| Secure | Secure なコード | Secure と Non-secure の両方にアクセスできる |
| Non-secure | Non-secure なコード | Non-secure のみ。Secure に触れると Fault |
重要: これは特権レベル(Privileged / Unprivileged)とは直交する。 つまり 4 通りの組み合わせがある。
| Privileged | Unprivileged | |
|---|---|---|
| Secure | Secure Privileged | Secure Unprivileged |
| Non-secure | Non-secure Privileged | Non-secure Unprivileged |
メモリ属性の設定
| 属性 | 意味 |
|---|---|
| Secure (S) | Secure 状態からのみアクセス可 |
| Non-secure (NS) | どちらからでもアクセス可 |
| Non-secure Callable (NSC) | Non-secure から呼び出せる Secure コードの入口だけを置く特別な領域 |
属性を決めるのは次のユニットである。
| ユニット | 内容 |
|---|---|
| SAU (Security Attribution Unit) | CPU 内蔵。ソフトウェアで設定できる。領域数は実装依存(典型 4〜8) |
| IDAU (Implementation Defined Attribution Unit) | チップベンダが固定で実装する。アドレスマップに従って属性を決める |
最終的な属性は SAU と IDAU の両方から決まり、より制限の強い方が勝つ。
NSC と SG 命令 — 越境の唯一の入口
Non-secure から Secure の機能を呼びたい。だが自由に飛び込めては困る。
SG命令がなぜ必要か.Non-secure から Secure への分岐は、「NSC 領域にあり、かつ最初の命令が SG である」 場合にしか許されない。 それ以外は SecureFault になる。
これで攻撃者は、Secure コードの途中に飛び込むことができない。 「署名関数の、権限チェックの直後にジャンプする」といった攻撃が原理的にできない。
ハードウェアで入口を 1 点に絞る——非常にうまい設計である。
越境時の注意点
| 注意点 | 内容 |
|---|---|
| 引数のポインタ検証 | Non-secure から渡されたポインタが本当に Non-secure 領域か確認する(TT 命令) |
| レジスタのクリア | Secure から戻るとき、使ったレジスタに秘密が残らないようにする |
| スタックの分離 | Secure と Non-secure でスタックポインタが別(MSP_S / PSP_S / MSP_NS / PSP_NS) |
| 割り込みの扱い | 割り込みごとに Secure / Non-secure のどちらで処理するか設定する |
/* Secure 側で、Non-secure から渡されたポインタを検証する */
#include <arm_cmse.h>
int32_t __attribute__((cmse_nonsecure_entry))
secure_sign(uint8_t *data, size_t len, uint8_t *out)
{
/* ★ data と out が本当に Non-secure 側の書ける領域か確認する */
if (cmse_check_address_range(data, len, CMSE_NONSECURE | CMSE_MPU_READ) == NULL)
return -1;
if (cmse_check_address_range(out, 64, CMSE_NONSECURE | CMSE_MPU_READWRITE) == NULL)
return -1;
/* ここから安全に処理できる */
return do_sign(data, len, out);
}この検証を忘れるのが、TrustZone で最も多いバグである.
Non-secure 側が
secure_sign(secure_key_address, 32, out)と呼んだら?検証がなければ、Secure 側が自分の鍵を読んで署名して返してしまう。 分離の意味が完全に失われる。この「Confused Deputy(混乱した代理人)」攻撃は、 特権分離のある全システムに共通する古典的な問題である。
Arm は CMSE(
arm_cmse.h)でチェック用の組み込み関数を提供している。必ず使うこと。
周辺回路とバスの分離
CPU だけ分けても足りない。DMA が Secure メモリを読めたら意味がない。
| 対象 | 分離の機構 |
|---|---|
| メモリ | SAU / IDAU + メモリ保護コントローラ |
| 周辺回路 | 周辺ごとに Secure / Non-secure を割り当てる(ベンダ実装) |
| DMA・他のバスマスタ | バスレベルのフィルタ |
| 割り込み | NVIC の各割り込みに Secure / Non-secure 属性 |
ベンダごとの名称:
| ベンダ | 名称 |
|---|---|
| ST | GTZC(Global TrustZone Controller)— TZSC / MPCBB / TZIC |
| Nordic | SPU(System Protection Unit) |
| NXP | AHB Secure Controller |
| Renesas | TrustZone Filter(バス・周辺ごとの属性設定) |
| Silicon Labs | SMU(Security Management Unit) |
ここがベンダごとに最も差が出るところである.
「TrustZone 対応」と書いてあっても、 DMA コントローラが分離されているか、 どの周辺を個別に割り当てられるか、 SRAM をどの粒度で分けられるか(ブロック単位か、バンク単位か) は品種ごとに違う。
4. TF-M(Trusted Firmware-M)
Arm が提供する、TrustZone-M 上のセキュア側ファームウェアの参照実装である。
TF-M では、通常のアプリケーションが動く Non-secure 側を NSPE (Non-secure Processing Environment)、TF-M 本体が動く Secure 側を SPE(Secure Processing Environment)と呼ぶ。
| PSA サービス | 役割 |
|---|---|
| Crypto | 暗号処理。鍵はハンドルで参照し、値を返さない |
| Internal Trusted Storage (ITS) | チップ内フラッシュに機密を保存する |
| Protected Storage (PS) | 外部メモリでも使えるよう、暗号化して保存する |
| Initial Attestation | アテステーショントークンを生成する(04 章) |
| Firmware Update | セキュア OTA の制御(13 章) |
PSA Crypto API の設計思想が優れている.
/* 鍵は「ハンドル」でしか触れない */ psa_key_id_t key_id; psa_import_key(&attributes, key_material, len, &key_id); /* 使うときはハンドルを渡すだけ。鍵の値は返ってこない */ psa_sign_hash(key_id, PSA_ALG_ECDSA(PSA_ALG_SHA_256), hash, hash_len, sig, sig_size, &sig_len);API に「鍵の値を取り出す」関数が、そもそも存在しない(エクスポート可能属性を 明示的に付けた場合を除く)。
こうすると、Non-secure 側のコードは鍵を漏らそうにも漏らせない。 これが「安全な API 設計」の見本である。
TF-M の採用状況:
| ベンダ | 対応 |
|---|---|
| ST | STM32L5 / U5 / H5 / WBA 向けに提供 |
| NXP | LPC55S6x / i.MX RT 向けに提供 |
| Nordic | nRF Connect SDK に統合(nRF5340 / 9160 / 54L) |
| Renesas | RA ファミリ向けに提供 |
| Infineon | PSoC 64 / Edge 向け |
| Silicon Labs | 一部対応 |
5. MPU による分離(TrustZone がない場合)
TrustZone がない Cortex-M0+/M3/M4/M7 でも、MPU で一定の分離はできる。
| できること | できないこと |
|---|---|
| 特権/非特権の分離 | セキュリティ状態の分離(Secure/Non-secure) |
| タスクごとのメモリ保護 | ハードウェアによる越境の完全禁止 |
| スタックオーバーフロー検出 | DMA の制限(バス側の機構が別途必要) |
| コード領域の書き込み禁止 | — |
MPU による分離は「TrustZone より弱い」ことを理解しておくこと.
決定的な違いは、特権昇格の可能性である。
- MPU: 非特権コードが SVC ハンドラの脆弱性を突けば、特権に上がれる → 特権に上がれば MPU 設定を書き換えられる → 鍵が読める
- TrustZone: Non-secure の特権コードでも、Secure には触れない → Secure 側にバグがない限り、越境できない
つまり TrustZone は信頼すべきコードの量を減らす(TCB の縮小)。 これが本質的な進歩である。
とはいえ MPU による分離でも、ないよりは遥かによい。 TrustZone がないチップでも、鍵を扱うコードを特権側に閉じ込めるべきである。
6. RISC-V の分離機構
| 機構 | 内容 |
|---|---|
| PMP (Physical Memory Protection) | 物理アドレスに対するアクセス制御。M モードが設定し、S/U モードに適用 |
| ePMP (Smepmp) | PMP の拡張。M モード自身にも制限をかけられる |
| 特権モード | M(マシン)/ S(スーパーバイザ)/ U(ユーザ)の 3 段 |
| WorldGuard | SiFive が提案する、TrustZone に近い多世界分離 |
| Smmtt など | メモリ隔離の標準化提案 |
RISC-V の分離は、まだ標準が固まりきっていない.
PMP は「Arm の MPU 相当」であって、「TrustZone 相当」ではない。 M モードのファームウェアに脆弱性があれば、全部が破れる。
ベンダ独自の拡張(SiFive の WorldGuard、Andes の拡張、 各社の TEE 実装)で埋めているのが現状である。
RISC-V ベースの SoC を選ぶときは、「TrustZone 相当の機構があるか」を 個別に確認する必要がある。 Espressif の ESP32-C6 は APM(Access Permission Management)ベースの ESP-TEE を提供している(20 章)。
7. Cortex-A クラスの分離
アプリケーションプロセッサでは、話が一段複雑になる。
| 層 | 内容 |
|---|---|
| TrustZone-A | Secure World / Normal World。EL3(モニタ)が切り替える |
| TEE OS | Secure World で動く小さな OS。OP-TEE が代表的 |
| Trusted Application (TA) | TEE OS 上で動く、署名されたアプリ |
| 仮想化 (EL2) | ハイパーバイザによる分離 |
| Armv9 CCA | Realm という新しい世界を追加。ハイパーバイザからも隔離される |
| 標準 | 内容 |
|---|---|
| GlobalPlatform TEE | TEE のインタフェース標準(TEE Client API、TEE Internal API) |
| Arm SMCCC | Secure Monitor Call の呼び出し規約 |
| OP-TEE | オープンソースの TEE 実装。Linaro が主導 |
Cortex-A で TEE を使う典型例:
- Android の鍵ストア(Keymaster / KeyMint)
- DRM(Widevine の高セキュリティレベル)
- 決済アプリの鍵保護
- 指紋認証のテンプレート保護
IoT ゲートウェイやエッジ AI 機器でも、 証明書と秘密鍵を OP-TEE の中に閉じ込めるという使い方をする。
8. 分離だけでは足りないもの
| 攻撃 | 内容 | 対策 |
|---|---|---|
| Confused Deputy | Secure 側に「自分のメモリを読ませる」 | ポインタ検証(本章 3 節) |
| サイドチャネル | Secure の処理を、Non-secure から消費電力やキャッシュで観測する | DPA 対策、キャッシュ分離(09 章) |
| フォールト | 越境チェックをグリッチで飛ばす | 冗長チェック(10 章) |
| Secure 側のバグ | Secure 側に脆弱性があれば終わり | Secure 側を小さく保つ(TCB 最小化) |
| DMA バイパス | バスマスタから Secure メモリを読む | バスレベルのフィルタ設定(本章 3 節) |
「Secure 側を小さく保つ」が最も重要な設計指針である.
Secure 側にコードを入れれば入れるほど、そこにバグが入る確率が上がる。 そして Secure 側のバグは、分離そのものを無効化する。
Secure 側に入れるべきもの:
- 鍵の取り扱い
- 暗号処理
- セキュアブート/OTA の検証
- アテステーション
Secure 側に入れてはいけないもの:
- TCP/IP スタック(大きすぎる)
- JSON / XML パーサ(入力処理は脆弱性の温床)
- ファイルシステム
- アプリケーションロジック
「入力を解析するコード」は Secure 側に置かない、が経験則である。
9. この章のまとめ
| ポイント | 内容 |
|---|---|
| 目的 | 「バグはある」前提で、鍵は取られない構造を作る |
| 核心 | 鍵を「使えるが読めない」——攻撃の規模を 1 台・一時的に閉じ込める |
| 3 つのレベル | 特権分離(MPU)/セキュリティ状態(TrustZone)/物理(別チップ) |
| TrustZone-M | Secure / Non-secure は特権レベルと直交する |
| NSC と SG | 越境の入口を 1 点に絞る。Secure コードの途中に飛び込めない |
| 最頻出のバグ | ポインタ検証の忘れ(Confused Deputy)。CMSE の関数を必ず使う |
| 周辺・DMA | CPU だけでは足りない。バス側の分離をベンダごとに確認する |
| TF-M / PSA | 鍵はハンドルでしか触れない API 設計。エクスポート関数がない |
| MPU との差 | MPU は特権昇格で破れる。TrustZone は TCB を小さくする |
| 最重要の指針 | Secure 側を小さく保つ。入力を解析するコードを入れない |
次章では、この全部の土台になっている—— 暗号エンジンと、それ以上に重要な乱数生成器を扱う。