zephyr

What does it do?

Zephyr is a real-time operating system (RTOS), meaning an operating system for real-time use, and it supports multiple hardware architectures and is built with security in mind. [1] It uses a small-footprint kernel, the core part of an operating system, for resource-constrained systems such as embedded environmental sensors, LED wearables, smart watches, and IoT wireless gateways. [2] The kernel supports ARM, Intel x86, ARC, Tensilica Xtensa, RISC-V, SPARC, and MIPS architectures. [3]

Activity, over time

Daily observations from the public event archive.

Attention

Daily stars & forks

Loading attention chart

Star accumulation

Cumulative stars

Loading star accumulation chart

Pull requests

Daily opens & merges

Loading pull requests chart

Conversations

Daily issues & comments

Loading conversations chart

Community

Daily accounts & reviews

Loading community chart

Releases

Daily releases

Loading releases chart

Mentions

9posts · 30d
Nov 30 posts
  1. X · · @crs

    github.com/zephyrproject-rtos/zephyr/releases/tag… v4.5.0出ました。現在PR残件2600超。2年前は1000件行かないぐらいだったか、さすがにリポジトリの分割という話題が出てくるのもわかる。

    40 views
  2. Cortex-MのTrustZoneを利用したIoT時代にMCUでセキュアに開発できる開発環境tRustedVisor-Mを作ってみた はじめに tRustedVisor-M は、Cortex-M 向けの TrustZone(以下 TrustZone-M)を利用し、IoT 機器のアプリを分離して動かしながら、個別に更新・検証するための開発環境である。Rust で実装した機器側の実行環境と、異常時の復旧・PC 上での検証を支える仕組みを組み合わせる。本記事では、その試作実装(PoC)を作成したので紹介する。 ネットにつながる機器は、出荷した後も、脆弱性の修正や機能の追加のために更新を続ける。Linux が動くアプリケーションプロセッサ(Cortex-A クラス)なら、アプリを VM やコンテナに分けて入れ替えられる。本プロジェクトは、Linux が載らない MCU(Cortex-M クラス)でも、アプリを一つずつ更新できる構成を目指している。 たとえば、更新データを受け取る処理、数を増やす counter、無限ループする hog が同居しているとする。counter だけを更新し、動かなければ counter だけを元へ戻したい。hog が戻らなくても、ほかの処理には実行機会を渡したい。 本記事では、この三つを動かす PoC を使って、要求から設計、動作の確認までを追う。 FreeRTOS や Zephyr で MCU 機器を開発している技術者に向けて書く。TrustZone-M・WebAssembly・Rust の知識は前提にしない。中心の問いは、TrustZone-M を備える Cortex-M の MCU で、アプリを分離して動かし、言語に縛られずに個別更新・検証するには、何を誰に任せればよいかである。その分担から、ハイパーバイザーの役割と、tRustedVisor-M が開発環境としてまとめる範囲の違いも説明する。 動作確認には二つの環境を使った。QEMU は PC 上で Cortex-M33 のボードを模擬し、機器用ファームウェアを動かす。もう一つは、同じアプリを PC 用のサービスと組み合わせて動かす机上シミュレータである。 出発点の三つの要求 第一は疎結合な更新である。counter を直すために、更新を受け取る処理まで作り直したくない。具体的には、次を満たしたい。 一つのアプリの更新で、ほかのアプリ、OS、通信のソフトを作り直したり、書き直したりしない。 開発元やチームが別々のアプリを、同じ機器に同居させる。 アプリを更新しても、保護を担う土台と、その正しさを信頼する範囲を変えない。更新したアプリの機能や、ほかの処理との組合せの確認まで不要になるという意味ではない。 一般アプリの更新に失敗しても、ほかのアプリを再起動せずに続ける。共有するランタイム自体の更新は対象が広がるため、後で分けて説明する。また、更新処理による実行の遅延がないことを意味するものではない。 第二は言語と ABI の非依存である。アプリを書く言語を選べるようにし、アプリのバイナリを、ファームウェアのコンパイラ、関数の呼び出し規約(ABI)、OS の版、SoC から切り離したい。その代わりに、機器と PC で共通の「アプリが呼べる関数の契約」を用意する。この契約をアプリ ABI と呼ぶ。契約が同じなら、一度ビルドしたアプリを、ファームウェアの更新後も、別の SoC や PC でも動かせる構成にしたい。 第三は更新の検証である。署名が正しい counter でも、起動後に動き続けるとは限らない。更新を受け取ってから採用するまでと、配布する前の確認をひとつながりで考える。 受け取った更新を、署名・内容のハッシュ・版で確かめてから受け入れる。 試しに起動して、採用条件を満たしたら確定し、満たさなければ元の版へ戻す。 配布する前に、同じアプリのバイナリを PC 上で動かして結果を確かめる。 既存のパッケージでは何ができるのか counter 一つを直す場面を、FreeRTOS 系、Zephyr 系、TF-M と RTOS の組み合わせ、RTOS 上の WebAssembly ランタイムと比べる。カーネルだけでなく、通信と更新のソフトを含めて見る。注目するのは、何を配り直すか、実行系が破られたときに誰が守るか、どの単位で試して戻せるかの三点である。 比較に必要な仕組みを先に説明する。WebAssembly(以下 wasm)は、決められた命令とメモリの規則に従うプログラム形式で、機器ではランタイムというソフトが読み込んで実行する。1 TrustZone-M は、Cortex-M の実行と資源を Secure と Non-secure の二つの状態に分ける仕組みだ。以下では Secure 側、NS 側と書く。CPU の特権・非特権は、保護設定などを操作できるかの区別であり、Secure/NS とは別の軸である。メモリ領域ごとに読み書きや実行を制限するハードウェアを MPU と呼ぶ。 FreeRTOS 系と Zephyr 系は、MPU を使った分離や、MCUboot と組み合わせた署名付き更新を利用できる。標準的なファームウェア更新の単位は、リンク済みのイメージ、つまり一緒に作って配るバイナリである。Zephyr の LLEXT なら拡張だけを読み込むこともできるが、メインのビルドとの結びつきや更新検証の条件が残る。TF-M は Secure 側に保護と更新サービスを置けるが、NS 側のアプリ同士の分離は RTOS に委ねる。2 3 4 5 6 7 容量の面では、TF-M と Zephyr の組み合わせは、Secure 側と NS 側の両方を含めて ROM(フラッシュ)と RAM を見積もる必要がある。Zephyr は、単体でスケジューリング、メモリ管理、ドライバなどを担う汎用 RTOS である。8 Secure 側の TF-M に加え、NS 側には Zephyr のカーネルや管理用データ、スレッドのスタックを保持する容量を要する。4 不要な機能は設定で削れる。8 本 PoC にもドメインごとの専用 RAM と wasm 実行器のコストがあり、同等構成での ROM・RAM の大小は比較していない。 wasm を使う Ocre は、アプリ一つのバイナリを配り、Linux と Zephyr で同じものを動かせる。一方、アプリを分ける境界はランタイムのサンドボックスであり、Ocre のコンテナは同じアドレス空間のスレッドとして動く。ランタイムのバグで境界が破られた場合への備えは、別に考える必要がある。9 10 11 既存の仕組みで要求の一部には答えられる。残るのは、アプリ単位の配布、実行系の欠陥を想定した分離、更新の検証・復帰を、同じアプリの単位でそろえることである。本 PoC は、その分担と強制する場所をまとめて設計する。 四つのパッケージの構成・全比較表・出典 以下は、使っている RTOS の列と本 PoC の列を詳しく比べるための資料である。上の三点を把握できたら、次の「要求から五つの機能を導く」へ進める。Zephyr の userspace は、非特権の user スレッドで処理を動かす仕組みを指す。 各パッケージは次のとおりである。 FreeRTOS 系:FreeRTOS の長期サポート版(202604.01-LTS)は、カーネルに TCP/IP スタック、MQTT・HTTP のクライアント、AWS IoT のジョブ通知とファイル転送のライブラリを組み合わせて配布する。起動時に更新を検証するブートローダは含まれず、製品や SDK ごとに選ぶ。12 選択肢の一つが MCUboot で、イメージごとの署名検証、巻き戻し防止、試行と確定・差し戻しを備える。2 Zephyr 系:Zephyr は、更新を「ファームウェアのバイナリを空いた領域へ書き、MCUboot の手順で入れ替える」流れとして説明している。更新の受信には hawkBit、UpdateHub、SMP(MCUmgr)、LwM2M などを使い、どれも受け取ったバイナリを MCUboot が検証する。13 Zephyr には、ELF 形式のコードを実行中に読み込み、メインのバイナリとリンクする LLEXT という仕組みもある。3 PC 上では、アプリをカーネルと一緒に Linux の実行ファイルとしてビルドする native_sim というボードで動かせる。14 TF-M + RTOS:TF-M(Trusted Firmware-M)は、Secure 側で PSA Crypto などの共有サービスを動かす。Zephyr の文書は、NS 側で Zephyr のアプリを動かし、PSA API で Secure 側を呼ぶ構成を説明している。起動時は、MCUboot をもとにしたブートローダが Secure 側と NS 側のイメージを検証する。4 15 ファームウェア更新のサービスも Secure 側に置ける。5 RTOS 上の wasm ランタイム:WebAssembly(以下 wasm)は、決められた命令とメモリの規則に従うプログラム形式である。機器では、wasm を解釈・実行するランタイムを組み込んで使う。1 Ocre は、Zephyr と Linux の上で WAMR というランタイムを使い、wasm のアプリを「コンテナ」として動かすプロジェクトである。Ocre は、wasm 向けの ABI である WASI Preview 1 に対してコンパイルした wasm を動かす。9 16 tRustedVisor-M の列では、アプリとその実行状態をまとめて Secure 側が分ける単位を、保護ドメイン(以下ドメイン)と呼ぶ。 比べる問い FreeRTOS 系 Zephyr 系 TF-M + RTOS RTOS 上の wasm(Ocre) tRustedVisor-M アプリ 1 つを直すとき、何を作り直して配るか ファームウェア全体。イメージを分けても単位はリンク済みのイメージ ファームウェア全体。LLEXT で拡張だけを読み込むこともできる NS 側のイメージ全体 wasm のイメージ 1 つ wasm のイメージ 1 つ アプリのバイナリは何に結びつくか カーネルの版・設定・コンパイラ・リンカ配置 カーネルの版・設定・ボード・ツールチェーン。LLEXT の拡張もメインのビルドに結びつく NS 側 RTOS のビルド WASI Preview 1。Linux と Zephyr で同じバイナリ 本プロジェクトのアプリ ABI。QEMU と PC で同じバイナリ 外部入力を解析する部分(通信スタック・ランタイム)のバグを突かれたとき、ほかのアプリのメモリを誰が守るか その部分を非特権のタスクに置けば、カーネルが MPU で守る。特権で動かした部分が破られると守れない その部分を user スレッドに置けば、カーネルが MPU で守る。カーネルと特権スレッドが破られると守れない NS 側のアプリ同士は RTOS 次第。Secure 側の資産は TF-M が守る 守る仕組みは無い。ランタイムのサンドボックスが境界で、コンテナは同じアドレス空間で動く Secure 側が、実行中のドメインに合わせて MPU を切り替えて守る(Secure 側が正しいことが前提) 暴走したアプリから、誰が CPU 時間を取り戻すか カーネルが優先度で切り替える。同じ優先度どうしは時間を分け合う カーネルが優先度で切り替える。CPU を独占されることは脅威の対象外 NS 側は RTOS 次第 Zephyr のスレッドとして、Zephyr と同じ Secure 側のタイマーがドメインを順に切り替える アプリが使える機能を、誰がどこで制限するか カーネルが、タスクごとのメモリ領域とカーネルオブジェクトの許可で制限する。許可はコードで与える カーネルが、user スレッドに許可したドライバとカーネルオブジェクトに制限する。許可はコードで与える Secure 側のサービスは PSA API で呼ぶ。NS 側は RTOS 次第 Ocre が、コンテナごとの capabilities で制限する Secure 側が、署名した許可情報(マニフェスト)の権利で制限する 更新を、どの単位で検証・試行・巻き戻しできるか イメージ単位。MCUboot と組めば署名・巻き戻し防止・試行と確定 イメージ単位(MCUboot)。LLEXT の文書は検証できると書くが、v4.4.2 の LLEXT に署名検証の処理は無い イメージ単位。試行と確定・棄却 確認した範囲では無い(取得したイメージのハッシュを表示するだけ) wasm 単位。Secure 側が署名・ハッシュ・版を検証し、試行中の版の生存確認で確定し、だめなら戻す 異常を起こしたアプリだけを止めて、やり直せるか 止める・作り直す処理はアプリ側で作る(カーネルに既定の処理は無い) 止めることはできる(例外を起こしたスレッドを止める)。作り直す処理はアプリ側で作る NS アプリの停止・再生成は、組み合わせる RTOS とアプリの実装次第 止まるが、やり直すには削除して作り直す(エラー状態から再開はできない) できる。Secure 側が RAM を消去して再起動し、繰り返せば隔離する 配布前に、同じバイナリを PC で動かせるか 作り直したバイナリ(POSIX ポート)。MPU の分離は試せない 作り直したバイナリ(native_sim)。userspace は無効 作り直したバイナリ(QEMU の評価ボード向け)。Arm のまま動く 同じバイナリ(Linux 版の Ocre) 同じ wasm(PC の机上シミュレータ) 表の根拠は次のとおりである。FreeRTOS は task.h・tasks.c・POSIX ポート 6 17 18、MCUboot は設計文書 2、Zephyr は userspace・LLEXT・致命的エラー・native_sim の文書とソース 7 3 19 20 21 14 22、TF-M は分離レベル・ファームウェア更新・panic の文書とソースと、Zephyr の TF-M 対応ボードの一覧 23 5 24 25、Ocre と WAMR はソースと文書 16 10 26 11 で確かめた。 Rust 製の wasm ランタイムである Wasmtime も、ランタイムやコンパイラのバグに備えた追加の防御を説明している。wasm の規則と、それを実装するランタイムの正しさを分けて考えるための参考になる。27 要求から五つの機能を導く まず、ファームウェアの版や CPU 命令に結びつかない形式でアプリを配るため、wasm を選ぶ。本 PoC のアプリは Rust から wasm へビルドする。本体の実装には Rust を使うが、アプリは共通のアプリ ABI に従う wasm を作れる言語で書ける。機器と PC はその ABI を提供するので、アプリを作り直さずに検証できる。 ここに、もう一つ条件を置く。本 PoC では、悪意あるアプリだけでなく、欠陥を突かれたランタイムが任意のネイティブコードを実行する場合まで想定する。 wasm を選べば必ず必要になる条件ではなく、本プロジェクトが選んだ脅威の範囲である。保護の成立のために正しく動くと信頼する部分を TCB(Trusted Computing Base)と呼ぶ。この範囲からランタイムを外すため、次の五つに役割を分ける。 ① メモリ分離と実行時間の割当て:counter と hog の作業用メモリを分け、hog が戻らなくても counter に実行機会を渡す。ランタイム自身が破られた場合にも備え、外側の Secure 側が強制する。 ② 権利の強制と UART 処理:更新を受け取る処理には通信と更新を許可するが、counter には不要な権利を与えない。Secure 側が、署名された許可と実際の呼出元を照合する。28 ③ アプリ・ランタイムの更新と復帰:counter の更新を検証してから試し、採用できなければ counter だけを戻す。共有ランタイムを更新する場合は、影響する対象を別に管理する。 ④ 異常検知・再起動・隔離:採用済みのアプリが異常を起こしたら、その処理だけの復旧を試みる。再起動しても繰り返すなら切り離す。29 ⑤ QEMU と PC で同じ wasm を検証:配布する counter と同じバイナリを PC でも動かし、機器向けの実行結果と判断を比べる。 解決策を合わせると、どんな構成になるのか tRustedVisor-M の機器側の PoC は、FreeRTOS や Zephyr に追加するライブラリではなく、Secure 側の管理処理と NS 側の wasm ランタイムからなる独自のファームウェア構成である。既存 RTOS が担うタスクの切替に相当する役割を、Secure 側の管理処理が担う。既存の RTOS タスクやそのバイナリを、そのまま動かす構成ではない。30 31 一つの wasm アプリと、そのランタイムの実行状態・専用 RAM を一組にする。この組を保護ドメイン(以下ドメイン)と呼ぶ。複数のドメインはすべて NS 側の非特権で動き、Secure 側が使えるメモリと実行順を切り替える。ランタイムの命令コードは共有できるが、書き換わる状態はドメインごとに分ける。 冒頭の三つは、更新を受け取るシステムドメイン、counter のドメイン、hog のドメインに対応する。外部入力の解析も wasm の実行も NS 側に置き、分離・権利・更新・復旧の最終判断は Secure 側へ集める。配布前の確認は、同じ wasm を動かす PC が受け持つ。 ここで比較の基準にするのは、複数の OS を仮想マシン(VM)として動かし、CPU やメモリなどを割り当てて互いを分離する、サーバー向けハイパーバイザーの基本的な役割である。32 tRustedVisor-M は、OS ごとの VM を用意せず、アプリごとのドメインを Secure 側で管理する。さらに、同じアプリを単位にした更新・復旧と、配布前の PC 検証までを一つの開発環境にまとめる。ハイパーバイザーの分離・資源管理という役割を超えて、アプリを作って配り、更新して戻すところまでを扱う構想である。 代償として、ドメインごとの RAM と、Secure 側で保護を切り替える処理が必要になる。Secure 側の正しさは信頼の前提に残る。そこを小さく保ち、確認する対象を絞る狙いであり、PoC が最小性や安全性全般を証明したわけではない。 この後は、①〜⑤の分担を図で確認し、観測で何が起きたかを読む。コードを追うためのソース配置と内部処理は参照用にまとめる。分離・権利・更新・復旧・検証を分けるのは、それぞれが別の要求に答え、詳しく追う処理の入口も異なるためだ。 対象読者 FreeRTOS や Zephyr などで、ネットにつながる MCU 機器を作っている組込み技術者を想定する。メモリ、割り込み、ファームウェア更新の基本を知っていればよい。TrustZone-M、wasm、Rust の知識は前提にしない。 当面の実装・検証ターゲットは Cortex-M33 とする。本命として見据えているのは、Cortex-M85 や Cortex-M55 を搭載した GHz 級のハイエンド MCU である。 観測には、ホスト PC 上の QEMU で Cortex-M33 の AN521 モデルを使う。RP2350 向けのコードもあるが、確認したのはビルドまでで、実機での動作は未確認である。特に、RP2350 での更新(フラッシュの消去・書込み)は検証していない。本記事で示すのは、設計、ソース確認、QEMU と PC 上の検証で確かめた範囲である。 用語集 本文でも使う場面で説明する。名前を確かめたいときは、次の一覧を参照してほしい。 本文で使う用語 用語 本記事での意味 wasm WebAssembly。決められた命令とメモリの規則に従うプログラム形式。機器と PC で同じアプリのバイナリを動かすために使う ランタイム wasm を読み込み、解釈・実行するソフトウェア。この PoC の機器側では Rust 製の wasmi を使う TrustZone-M Cortex-M の実行と資源を、Secure と Non-secure の二つの状態に分ける仕組み Secure / Non-secure TrustZone-M の二つの状態。Secure 状態の側(Secure 側)が保護を管理し、Non-secure 状態の側(NS 側)でアプリとランタイムを動かす 特権 / 非特権 保護設定などを操作できるかを分ける CPU の実行権限。Secure / NS とは別の区分 TCB Trusted Computing Base。保護が成立するために、正しく動くことを前提とする部分 保護ドメイン Secure 側がメモリ・実行時間・権利を分けて管理する実行単位。この PoC では、一つの wasm アプリと、そのランタイムの状態を含む 権利とマニフェスト 権利は、ログ・UART・更新などを使ってよいという許可で、CPU の特権とは区別する。マニフェストは、ドメイン番号・版・権利・アプリのハッシュを書いた署名対象の記述 アプリ ABI wasm アプリが外部の機能を呼ぶための関数と呼び出し規約。ランタイムの種類から独立させた契約 試行・確定・版の下限 試行は新しい版を期限付きで動かすことで、確定はその版を稼働する版として採用すること。版の下限は受け付ける最も古い版で、確定のたびに進む TrustZone-M の状態の区分と、CPU の特権・非特権は別の区分である。外部入力を受けて更新などを進める処理(後述のシステムドメイン)も、NS かつ非特権で動く。通信や更新の権利が多くても、CPU の保護設定を変更する特権は持たない。33 誰が何を守るのか ドメインとは何か アプリを一つずつ分離し、更新や復旧の対象にするには、何を一組として管理するかを決める必要がある。この PoC では、一つの wasm アプリと、それを動かすランタイムの実行状態・専用 RAM をまとめた単位を、保護ドメイン(以下ドメイン)と呼ぶ。Secure 側が、ドメインごとに使えるメモリ、実行時間、権利を管理する。 たとえば counter と hog を別々のドメインに置けば、それぞれが専用の作業用メモリと実行状態を持つ。同じ版のランタイムの命令コードは共有し、実行中に書き換わる状態はドメインごとに分ける。 アプリドメインとシステムドメイン ドメインには、一般のアプリを動かすアプリドメインと、通信や更新の前段処理を担うシステムドメインがある。どちらも wasm アプリをランタイムで動かし、NS 側の非特権で実行する。 種類 担当する処理 この PoC の例 アプリドメイン 機器の用途に応じたアプリの処理 counter、hog。それぞれ別のドメインとして動く システムドメイン 外部入力の受付や、更新データの組立て・提出 UART で更新データを受け取り、Secure 側へ提出する wasm システムドメインには通信や更新を進める権利を与える。名前の「システム」は担当する処理を指し、CPU の実行権限はアプリドメインと同じである。保護設定の変更や、更新を受け入れるかの最終判断は Secure 側が担う。33 なぜシステムドメインとして分けるのか 更新を受け取る処理は、機器外から届くバイト列を解析する。その解析に欠陥があっても、保護を担う部分や一般アプリへ被害を広げず、受信処理だけを更新・復旧できるようにしたい。このため、次の三つを分けている。 Secure 側との分離:受信データの組立てやプロトコルの解析は NS 側へ置く。Secure 側に複雑な外部入力処理を持ち込まず、正しさを信頼する範囲(TCB)を絞る。システムドメインの前段チェックを信頼せず、Secure 側が署名・内容・版・権利を検証し直す。 一般アプリとの分離:受信処理にも専用 RAM と実行時間を与え、UART と更新提出の権利をそこへ集める。counter や hog には、それらの権利を与えない。受信処理が乗っ取られても、他ドメインの RAM や保護設定へ届かないようにする。 更新・復旧の単位の分離:受信処理自身も wasm として動かし、アプリと同じ分離・更新・復旧の仕組みを使う。通信処理を直すために一般アプリを配り直す必要がなく、異常時もシステムドメインだけを再起動・隔離できる。 システムドメインが止まれば、一般アプリは続行できても、新しい更新は受信できなくなる。また、与えた UART・更新提出の権利の悪用までは防げない。分離で抑えるのは、その被害がほかのドメインや Secure 側へ広がることである。 機能一覧 TrustZone-M で、保護を管理する Secure 側と、アプリ・ランタイムを動かす NS 側を分ける。NS 側のドメイン同士は MPU で分離し、Secure 側が各ドメインに必要な資源と権利だけを割り当てる。さらに、同じアプリ ABI を持つ PC 上の机上シミュレータを、配布前の検証に使う。①〜④は機器の上で動く実行環境の機能、⑤はアプリを作って確かめるための開発環境の機能である。 機能 主な担当 この例で守るもの ① メモリ分離と実行時間の割当て Secure 側 hog と counter のメモリを分け、ほかの処理へ実行機会を渡す ② 権利の強制と UART 処理 Secure 側とシステムドメイン 受信データの組立てはシステムドメイン、権利の最終照合は Secure 側 ③ アプリ・ランタイムの更新と復帰 Secure 側 更新対象を試し、採用するか戻すかを決める ④ 異常検知・再起動・隔離 Secure 側 異常のあるドメインを復旧し、ほかを巻き込まない ⑤ QEMU と PC で同じ wasm を検証 PC の机上シミュレータ 配布するものと同じアプリの結果と、サービスの判断を比較する 全体図 wasm からランタイムへの入口をアプリ ABI、ランタイムから Secure 側への入口を Secure API と呼ぶ。この二つは別の境界である。図の「保護管理」は、Secure 側が担う①〜④をまとめたものだ。 実線は実行時の呼出しや下層への依存を表す。点線は共有コードの利用と、検証時に同じバイナリを使う関係を示す。PC は機器の実行時の呼出先ではない。図では一般アプリを一つだけ描いたが、hog も counter とは別のドメインで、別の状態と RAM を持つ。 図の解説 ① メモリ分離と実行時間の割当て 一つのランタイムが乗っ取られたとき、そのランタイムと同じドメインのデータや計算結果は守れない。それでも、ほかのドメインの作業用メモリには届かないようにする。 そのために、各ドメインに専用 RAM を与え、実行中のドメインの RAM だけを読み書きできるようにする。ドメインを切り替える際には、Secure 側が、NS からアクセスできる領域も切り替える。アプリとランタイムを非特権にすることで、自分でその設定を緩める操作を防ぐ。 CPU 時間は、実行できるドメインを固定の順で巡回し、各ドメインに同じ長さの時間枠を渡すラウンドロビンで管理する。この時間枠をスライスと呼ぶ。Secure 側のタイマーがスライスの終わりに実行を中断し、次のドメインへ移す。アプリが自発的に処理を返すのを待たないため、無限ループする hog からも実行を取り戻せる。30 現在の三つなら、SysDom0(更新受信)→ AppDom1(counter)→ AppDom2(hog)→ SysDom0 → … の順になる。隔離したドメインは飛ばす。システムドメインを高優先度にすることや、処理の量に応じた重み付けはしない。固定順に絞ることで、TCB に含まれるスケジューラの判断を単純に保つ。30 メモリの分離と時間の割当ては、役割が異なる。前者は他者のデータへのアクセスを防ぎ、後者はほかの処理に実行機会を与える。 時間の割当ての成立条件は、タイマーが正しく割り込み、Secure 側の切替処理が動くことに加え、その割り込みが NS の実行中に届くことである。Secure API の処理中に届いた割り込みではその場でドメインを切り替えず、NS 側への復帰時に切替を持ち越すため、そのドメインの実行時間が延びうる。wasm アプリの無限ループは NS 側で回るので、切り替えられる。一方、乗っ取られたランタイムが Secure API を呼び続けた場合に切替がどこまで遅れるかは、この PoC では評価していない。 ② 権利の強制と UART 処理 システムドメインには、通信の受信や更新を進める権利を与える。一方、一般のアプリには、その用途で必要な権利だけを与える。権利は、配布側が対象ドメイン・版・権利・アプリのハッシュを記して署名する記述(マニフェスト)に含める。 ランタイムは、アプリが使おうとする関数を権利と照合して早期に拒否する。さらに Secure 側も、サービスを呼んだ時点の実行中ドメインに基づいて権利を照合する。呼出側が名乗った番号をそのまま信用しない。周辺機器への直接アクセスも、Secure 側が設定するメモリ保護で制限する。 UART 受信では、Secure 側が割り込みを受け、データが届いたという通知を保持する。通知を受けたシステムドメインが、許可された入口からバイトを読み、更新データを組み立てる。Secure 側は、割り込みの受付と通知、権利の照合、読み出しの仲介を担い、外部プロトコルの組立てはシステムドメインに置く。外部入力を解析する処理が乗っ取られても、Secure 側の保護設定には届かない。ただし、UART の権利を持つドメインには、UART のレジスタへの直接アクセスもメモリ保護で開いている。UART の設定変更を Secure 側だけに限る構成にはなっていない。 権利のあるドメインが乗っ取られた場合、その権利の範囲での悪用までは防げない。たとえば UART を使う権利を与えた処理が、不都合な受信処理を行う可能性は残る。守る境界は、許可されていない資源や他ドメインへの波及である。 なぜ割り込みを Secure 側で受けるのか(ほかの方式との比較) この PoC では、時間の割当てに使うタイマーの割り込みも、UART の割り込みも、Secure 側が受ける。NS 側には CPU の特権で動くコードを置かず、すべてのドメインを非特権で動かすためである。割り込みを処理するコードは特権で動くので、NS 側で割り込みを受けるなら、NS 側に特権のコードが要る。 本 PoC は、乗っ取られたランタイムと、外部入力を解析するために最も狙われやすいシステムドメインを、攻撃者が動かしうる部分として扱う。NS 側の特権のコードが乗っ取られると、ドメインを分けるメモリ保護の設定を書き換えられ、すべてのドメインに届く。割り込みを Secure 側で受ければ、NS 側に乗っ取られて困る特権のコードが残らない。 UART のような周辺機器の割り込みについて、検討した方式は次の四つである。採用した方式は、Secure 側が割り込みを受けて通知だけを保持し、ドライバは権利を持つドメインが非特権で動かす。二つ目は、NS 側の特権のコードが割り込みとドライバをまとめて持つ方式である。三つ目は、割り込みの受付だけを担う小さな特権のコード(以下、受付部)を NS 側に置き、ドライバは非特権で動かす方式である。四つ目は、Secure 側がドライバまで持つ方式である。 比べる問い Secure 側が受け、通知だけ渡す(採用) NS 側の特権のコードが割り込みとドライバを持つ NS 側の特権の受付部が受け、ドライバは非特権 Secure 側がドライバまで持つ 外部から届いたバイト列を、誰が解析するか NS 非特権のシステムドメイン NS 側の特権のコード NS 非特権のドライバ Secure 側 NS 側のコードが乗っ取られたとき、ほかのドメインへ届くか 届かない。NS 側に特権のコードが無い 届く。特権のコードがメモリ保護の設定を書き換えられる 受付部が乗っ取られると届く。ドライバだけなら届かない 届かない。NS 側に特権のコードが無い 問題を起こした部分だけを止めて差し替えられるか できる。ドライバを持つドメインだけを再起動する できない。特権のコードは全ドメインに共通で、NS 側全体の再起動になる ドライバだけならできる。受付部は差し替えられず、NS 側全体の再起動になる Secure 側の更新になり、本 PoC の更新経路の対象外 受信の遅れや取りこぼしに強いか 弱い。読み出しは、ドライバを持つドメインに実行順が来るまで待つ。受信の限界は未評価 強い。割り込みの中ですぐ読める。ただし割り込みの中で長い処理をすると、優先度の低い割り込みが止まる 強い。割り込みの直後に受信データを受信バッファへ移せる 強い。Secure 側がすぐ読める 正しさを信頼する範囲(TCB)に何が加わるか 割り込みの受付と通知、時間の割当て NS 側の特権のコード全体 NS 側の受付部 外部入力の解析まで加わり、最も広い 本 PoC での扱い 採用 却下 通知方式の遅れや受信のあふれが観測されたら移る候補 却下。外部入力を NS 側で受ける方針と矛盾する 二つ目と三つ目の方式で、NS 側のメモリ保護の設定を Secure 側から固定できる SoC もある。ただし SoC に依存し、RP2350 にあるかは未確認である。また、固定すると NS 側の特権のコードがドメインの切替時に設定し直せなくなるため、特権のコードに分離を任せる構成とは両立しない。 タイマーの割り込みも、同じ理由で Secure 側が受ける。アプリが自発的に処理を返すのを待つ協調型の切替は、戻らない hog には効かない。ランタイムが wasm の実行量を数えて区切る方式は、乗っ取られたランタイムが数えるのをやめれば効かない。Secure 側のタイマーなら、NS 側が何をしていても実行を中断できる。代わりにスケジューラが TCB に入るため、方式を固定の順番回しに絞った。30 保護違反などのフォールトも Secure 側へ届くように設定している。このため、④の検知・再起動・隔離も NS 側の協力を必要としない。34 35 ③ アプリ・ランタイムの更新と復帰 更新の置き場を二つ用意し、一方で動かしながら、もう一方へ新しいイメージを書く。この置き場をスロットと呼ぶ。Secure 側が署名・内容のハッシュ・版を検証し、受け入れた版を期限付きで試す。 counter を更新する場合は、そのドメインだけを新しい wasm で起動し直す。初期化を終えて生存確認を送れば、Secure 側が採用を確定する。確定せずに異常や期限切れを検知したら、counter だけを確定済みの版へ戻す。ほかのドメインは再起動しない。36 30 一般アプリに更新を提出する権利を与えずに確定させるため、この PoC では、アプリに確定専用の呼出しを求めず、生存確認を採用条件にした。確定が示すのは、生存確認を送れたことまでで、正しい動作の証明ではない。 署名も、配布元と内容の対応を確かめるもので、バグがないことを保証しない。 共有するランタイムの更新は、一つの wasm の更新とは影響範囲が異なる。 更新するもの 起動し直す対象 失敗時に戻す対象 counter の wasm counter のドメインだけ counter だけを確定済みの版へ戻す 共有ランタイム 各ドメインを一つずつ新版で起動 新版へ移したドメインを旧版で起動し直す。未移行のドメインは文脈を保つ ランタイム更新では、Secure 側が対象の生存確認を順に受け、全対象の移行後に確定する。30 37 どちらの更新でも、Secure 側の検証・書込み中に NS 側が実行できない時間はある。「ほかを再起動しない」ことと、遅延なく動くことは分けて考える。 確定の条件と、古い版への巻戻し防止 更新するアプリには生存確認の権利を必須とし、権利がない版は書込み前に拒否する。初期化中に生存確認を送っても、続く読み込みが失敗した版は確定しない。起動し直す前の旧版の生存確認でも確定しない。Secure 側が、初期化完了と試行中の版を照合するためである。36 30 確定したときだけ、受け付ける版の下限を進める。試行に失敗したときは確定済みの版へ戻せるが、採用後に下限より古い版を受け付けることはない。この下限を再起動後も維持するには、消えない保存領域が必要になる。AN521 の QEMU は RAM を保存領域に使うため、QEMU を起動し直すと下限も初期値に戻る。 ランタイムのコードは版ごとに置き、ドメイン番号の順に新しい版へ移す。移行中は旧版と新版のコードがフラッシュに並存する。稼働対象に生存確認の権利がないドメインがあれば、書込み前に更新の開始を拒否する。37 ④ 異常検知・再起動・隔離 保護違反などのフォールトと、必要な周期で届く生存確認の途絶を異常として扱う。確定済みの版で異常を検知したら、対象の作業用メモリを消去し、同じ版を起動し直す。 初期化後の通常稼働では、生存確認の権利を与えたドメインだけを期限監視する。通常シナリオの hog にはこの権利を与えず、①の時間割当てを確かめる。初期化中は、この権利の有無にかかわらず完了までの期限を監視する。30 異常を繰り返す場合は、再起動を続けても回復しない。PoC では、健全期間による検知回数のリセットを挟まずに三回検知すると、権利を取り消して実行順から外す。これを隔離と呼ぶ。ほかのドメインは実行を続ける。 生存確認は、その処理が正しい計算をしていることや、侵害されていないことの証明ではない。生存確認を送り続ける攻撃は、これだけでは検知できない。 更新の試行中の異常は、確定済みの版の異常と分けて扱う。試行中の版を何度も再起動するより、まず確定済みの版へ戻す。 ⑤ QEMU と PC で同じ wasm を検証 wasm アプリから見える関数と呼び出し規約を揃えれば、機器向けの実行環境と PC 上の机上シミュレータで、同じアプリのバイナリを動かせる。作り直したバイナリではなく、配布するものと同じバイナリを PC で試せることが、言語と ABI の非依存から得られる利点である。 QEMU では、Secure 側と NS 側のファームウェアを動かし、CPU の保護設定や例外処理を含めて確かめる。机上シミュレータでは、署名や権利、更新の判断を PC 上で提供する。実行結果を比較することで、実行環境を変えたときの契約のずれを探せる。 ログが一致しても、メモリ保護や割り込み、実時間性能が等しいことにはならない。それぞれの検証対象を分けて使う。 Rust の実装はどう分かれているのか ここは、①〜⑤をソースに対応づけるための案内である。対応表と全体図で配置を確認し、個々のコードを追わない場合は、次章「動かすと何が起きるのか」へ進める。 対応表 以下のソースパスはリポジトリ直下を起点とする。メモリ領域のアクセス可否を設定するハードウェアを MPU(Memory Protection Unit)、Secure / NS の属性を設定するハードウェアを SAU と呼ぶ。MPU は領域ごとに非特権アクセスや書込み・実行の可否を制限できる。38 Secure API の呼出入口には、NS から呼べる Secure 側の関数入口として SAU で指定する NSC(Non-secure Callable)領域を使う。 論理機能 実現方法 主なソース 状態 ① メモリ分離と実行時間の割当て 非特権起動、専用 RAM、NS の MPU 切替、Secure SysTick による文脈切替 env/src/nonsecure.rs:8、env/src/mpu_ns.rs:34、env/src/sched.rs:175 実装済み ② 権利の強制と UART 処理 署名検証、import の照合、実行中ドメインの権利照合、UART 割り込み通知 env/policy/src/lib.rs:89、runtime/host/src/adapter.rs:26、env/src/secure_api.rs:45、env/src/uart.rs:26 実装済み ③ アプリ・ランタイムの更新と復帰 スロットへの書込み、状態記録、試行・確定・復帰 env/src/update.rs:131、env/src/native.rs、env/src/storage.rs 実装済み ④ 異常検知・再起動・隔離 フォールト処理、生存確認の期限、対象 RAM の消去、権利取消し env/src/fault.rs:9、env/src/sched.rs:450、env/policy/src/recovery.rs:25 実装済み ⑤ QEMU と PC で同じ wasm を検証 共通 ABI とポリシー、wasmi / wasmtime の切替え、ログ比較 runtime/abi/src/lib.rs:6、sim/src/lib.rs:34、tests/parity.py:13 実装済み ①は MPU の制限とタイマーの両方が必要である。②も、ランタイムでの関数単位の照合と、Secure 側での照合・ハードウェア設定を組み合わせる。片方をもう片方の代わりにはしない。39 30 28 全体図 Runtime と Env の間の | が Secure / Non Secure の境界である。Runtime の中は counter の AppDom1、hog の AppDom2、更新受信の SysDom0 と、Runtime のコアに分ける。Env の中は、起動・保護、スケジューリング・復旧、API、UART、更新の担当に分ける。 不揮発資源、機器外との通信、更新は env が管理する方針とする。現行 PoC の更新手段は UART であり、USB/Ethernet は追加候補で、ドライバも更新経路も未実装である。[1]〜[4] は表の①〜④に対応し、NS unprivileged は NS 非特権、private RAM はドメイン専用 RAM、Nonvolatile storage は不揮発資源を表す。 Non Secure | Secure +---------------------------------+ | +---------------------------------+ | Runtime (Non Secure) | | | Env (Secure) | | NS unprivileged / runtime/ | | | env/ | | | | | | | +-----------------------------+ | | | +-----------------------------+ | | | AppDom1 : counter wasm | | | | | [1] Boot / memory setup | | | | runtime/app/counter/ | | | | | src/sau.rs / nonsecure.rs | | | | Runtime state + private RAM | | | | | src/mpu_ns.rs | | | +-----------------------------+ | | | +-----------------------------+ | | | | | | | +-----------------------------+ | | | +-----------------------------+ | | | AppDom2 : hog wasm | | | | | [1,4] Scheduler / recovery | | | | runtime/app/hog/ | | | | | Round-robin | | | | Runtime state + private RAM | | | | | src/sched.rs / fault.rs | | | +-----------------------------+ | | | +-----------------------------+ | | | | | | | +-----------------------------+ | | | +-----------------------------+ | | | SysDom0 : system wasm | | | | | [2] Secure API / rights | | | | runtime/system/ | | | | | src/secure_api.rs | | | | UART RX / assemble / submit | | | | +-----------------------------+ | | | Runtime state + private RAM | | | | | | +-----------------------------+ | | | +-----------------------------+ | | | | | | [2] UART IRQ / notification | | | +-----------------------------+ | | | | src/uart.rs +-+-------------------------------+ | | Runtime core / host/src/ | | | | +-----------------------------+ | | | | wasm exec / import rights | | | | | | | | adapter.rs / heap.rs | | | | +-----------------------------+ | | | | Secure API call (NSC) | | | | | [3] Verify / commit / undo | | | | | secure_api.rs | | | | | src/update.rs / native.rs | | | | | Per-domain runtime state | | | | | src/storage.rs +-+-+ | | +-----------------------------+ | | | | Slots / version floor | | | | | | | | +-----------------------------+ | | | +----------------+----------------+ | +-----------------+---------------+ | | | | | | +----------------+------------------------------------------+---------------+ | | | CPU : Cortex-M / TrustZone-M | | +------------------------+ | | +-----------------------------------------------------------------------+ | | | UART +--+ | | Execution core / registers (Runtime + Env) | | | | IRQ / RX-TX | | +-----------------------------------------------------------------------+ | | +------------------------+ | | | | +-----------------------------+ | | +------------------------+ | | Env-only control | | +-+ Nonvolatile storage | | | SAU / MPU_NS setup | | | Erase / write | | | Secure SysTick | | +------------------------+ | | NVIC (Secure IRQ) | | | +-----------------------------+ | +---------------------------------------------------------------------------+ Runtime の上側にある各ドメインでは、wasm からアプリ ABI を呼び、Runtime のコアが受ける。コアは別のドメインではなく、各ドメインで使う共通の実装である。同じ版の命令コードは共有するが、実行状態と専用 RAM は共有しない。内枠に書いた専用 RAM はそのドメインへの割当てを表す。Env も RAM を使う。 runtime/host と env は別のネイティブイメージとしてビルドする。Runtime コア内のファイル名は runtime/host/src/、Env 内の src/ は env/ を起点とする。外の資源へ延ばした線は使用・管理の関係を示し、箱の上下の配置は処理順を表さない。CPU 内の命令実行・レジスタは両側が使う。SAU・MPU_NS の設定、Secure SysTick、Secure IRQ の NVIC 制御は Env だけが操作する。MPU のアクセス制限は Runtime にも適用される。 境界を越える呼出しには Secure API を使う。アプリ ABI を受ける runtime/host/src/adapter.rs が wasm を実行し、必要な操作を runtime/host/src/secure_api.rs から NSC 経由で env/src/secure_api.rs へ渡す。Secure 側は呼出元の権利を照合し、実行管理、更新、UART の担当へ渡す。通知・読出しの結果は逆向きに戻る。28 40 更新の経路は、機器外の送信元 → UART → env の割り込み通知・読出し → システムドメインでの組立て → Secure API → env の検証・書込みである。更新先は、counter などのアプリ wasm、受信処理自身のシステム wasm、共有 Runtime のネイティブイメージである。wasm はドメインごとのスロットへ、Runtime は共有コード用のスロットへ書く。env 自体の更新は、この経路の対象に含めない。41 36 外付け資源の接続線は、その資源を使う側・管理する側を示す。不揮発資源の消去・書込み、更新記録と版の下限は env が管理し、NS 側は wasm 保存領域を読み出せる。現行実装では、UART 権利を持つドメインにレジスタの直接アクセスも許すため、外部通信を env の API 経由に限定する構成にはなっていない。39 42 AN521 の保存領域は RAM による代用であり、QEMU の再起動をまたぐ永続性はない。43 起動時には env/src/sau.rs で Secure / NS の属性を設定し、env/src/nonsecure.rs で NS を非特権で起動する。env/src/sched.rs はドメインの切替時に env/src/mpu_ns.rs でメモリ保護を切り替え、更新の試行期限・確定・復帰も管理する。Secure SysTick とフォールトも、実行管理・復旧の入口となる。platform はレジスタ定義や機種別操作を提供し、env がそれらを使って HW を設定する。30 39 同じ版のランタイムの命令コードはドメイン間で共有するが、wasmi の実行状態とヒープはドメインごとに持つ。共有する判断の規則は env/policy/ にあり、runtime/host の import の権利照合と、env の署名・権利・更新・復旧の判断に使う。⑤の PC 検証では、sim/src/lib.rs が同じ wasm を動かし、runtime/abi/ の契約と env/policy/ の規則を使う。44 ソースツリーと各フォルダの役割 主要な配置は次のとおりである。 (リポジトリ直下) ├── env/ Secure 側のファームウェア │ ├── policy/ 署名・権利・更新・復旧の判断 │ └── src/ 保護設定・切替え・UART・保存 ├── runtime/ │ ├── abi/ アプリ ABI │ ├── host/ NS のネイティブランタイム │ ├── system/ システムドメインの wasm │ └── app/ counter / hog の wasm ├── platform/ arch / cpu / board の定義と操作 ├── sim/ PC 上の机上シミュレータ ├── tests/ シナリオと実行結果の比較 ├── scripts/ QEMU 実行・開発用鍵生成 └── Makefile ビルドと検証の入口 フォルダ・ファイル 全体での位置づけ 実装しているもの 責務 対応機能 リポジトリ直下 ソースの起点 Cargo ワークスペースと実行手順 ビルド対象をまとめる ①〜⑤ env/ Secure 側の実行イメージ 起動と保護管理 最終的な分離・権利・更新・復旧を強制する ①〜④ env/policy/ 複数環境で使う判断 署名検証、権利照合、更新・復旧の状態遷移 判断の規則を共有する ②〜⑤ env/src/ ハードウェアへ接続する処理 SAU・MPU、文脈切替、UART、スロット管理 判断をファームウェア上で実行する ①〜④ runtime/ 実行基盤とドメイン実装 host、ABI、システムドメイン、一般アプリ アプリ実行側をまとめる ①〜⑤ runtime/abi/ アプリとの契約 import / export の名前と呼出規約 アプリを実行器から独立させる ②・③・⑤ runtime/host/ NS のネイティブイメージ wasmi の接続、ヒープ、各ドメインの初期化 専用 RAM 内で wasm を実行する ①〜④ runtime/system/ システムドメインの wasm UART の読み出し、更新データの組立て、生存確認 外部入力の処理を進める ②〜④ runtime/app/ 一般アプリの wasm counter と hog 通常処理と暴走時の継続、一般アプリの更新を検証する ①・③・⑤ platform/ arch・cpu・board ごとの接続点 機種別の資源設定、ログ、UART・保存操作 ハードウェア固有部分をまとめる ①〜④ sim/ PC 上の検証環境 アプリ ABI、サービス、実行器の切替え 同じ wasm の結果と判断を比較する ⑤ tests/ 自動検証 UART、更新(システムドメイン・一般アプリ・ランタイム)、復旧、ログ比較のシナリオ 想定する境界が働くかを確かめる ①〜⑤ scripts/ 実行の補助 QEMU 起動・ログ復号、開発用鍵生成 再現手順をまとめる ②・⑤ Makefile 利用者が呼ぶ入口 ビルド、QEMU、sim、検査ターゲット ターゲット別の手順を順に実行する ①〜⑤ 図の解説 機器では、runtime/host がアプリ ABI の呼出しを受け、必要な操作を Secure API で env へ渡す。env は env/policy の規則で判断し、platform や CPU の保護機構を使って強制する。PC の sim は同じアプリ ABI と判断の規則を使うが、CPU のメモリ保護を再現するものではない。 以下は①〜⑤をソースから追うための参照情報である。 五つの機能の内部処理とコードの入口 ① ランタイムごとの状態を、Secure 側が切り替える env/src/nonsecure.rs:15 は NS を非特権で起動する。env/src/mpu_ns.rs:34 の select は、実行するドメインに合わせて使う版のコード領域と、書込み可能な RAM・UART の領域を設定する。Secure のタイマー処理(env/src/sched.rs:175)は、レジスタとスタック位置を保存・復元し、この設定も切り替える。39 30 ランタイムの命令コードは、同じ版を使うドメイン間で読取り専用領域を共有する。移行中は旧版と新版が並存し、各文脈に合わせてコード領域と例外ベクタを選ぶ。一方、ヒープ、スタック、wasm の線形メモリ、実行器の状態はドメインごとに持つ。ネイティブコードが使う一部の静的状態も、切替時にドメインごとに保存・復元する。現在の実装は、一つのランタイム状態を全アプリで共有する構成ではない。 この PoC ではアプリを Rust で書いているが、Rust のメモリ安全性を、ランタイムが任意コード実行に陥った後の分離の根拠にはしていない。その状態でのアクセス制限は、Secure 側が設定するハードウェアに任せる。 ① 固定ラウンドロビンの選択と時間割当て 次のドメインは env/src/sched.rs:311-318 で選ぶ。現在の番号の次から巡回し、隔離されていない最初のドメインを選ぶ。隔離されていない全ドメインの初期化が完了すると、dom0 を探索の起点として通常の巡回を始める。実行可能な対象がなければタイマーを止める。30 次の探索位置 = (現在のドメイン番号 + 1) mod ドメイン数 探索位置から番号順に巡回し、隔離されていない対象を一つ選ぶ 実行状態とメモリ保護を切り替え、その対象の実行を再開する Secure SysTick の周期は SLICE_CYCLES で決め、AN521 は 20,000 サイクル、RP2350 は 150,000 サイクルである(それぞれ platform/board/an521/mod.rs:7、platform/board/pico2/mod.rs:11)。これをそのまま実時間の保証には読み替えない。RP2350 の値は実機で未校正である。43 45 アプリの step が早く終わった場合、Runtime は WFI(割り込み待ち)に入る。次のドメインの選択はタイマーに任せ、余った時間を即座に渡す方式ではない。hog のように step から戻らなくても、Secure 側のタイマーが切り替える(runtime/host/src/main.rs:32-69)。31 なお、タイマー割り込みを数える tick と、通常実行の割当てを数える slice は別である。初期化中も tick は進むが、slice 番号を進めるのは隔離されていない全対象の初期化完了後である。Secure API の実行中はその場で文脈を切り替えず、NS への復帰時に切り替える仕組みを持つため、各ドメインが実際に占有する時間が常に同じとは限らない(env/src/sched.rs:187-217,294-318)。30 ② 二つの API 境界で役割を分ける アプリ ABI は runtime/abi/src/lib.rs にある。wasm の log や heartbeat などを runtime/host/src/adapter.rs が実装し、必要な操作を Secure API に渡す。Secure API は env/src/secure_api.rs の NSC 入口で受ける。28 40 たとえばログ出力では、Secure 側が現在のドメインのマニフェストを取り出し、LOG 権利を照合する(env/src/secure_api.rs:45-50)。呼出側のタグに別ドメインの番号を入れても、その番号の権利へ切り替わることはない。UART の読み出しでも権利を確認し、渡せるレジスタ番号を限定する。28 42 runtime/system は通知を確認して UART データを読み、長さ付きの更新データを組み立てる。Secure 側は、この前段処理が正しいと仮定せず、受け取った範囲・サイズ・署名・内容・版を検査する。41 36 ③ アプリの更新と、共有コードの更新を分ける wasm 更新の処理は env/src/update.rs、ネイティブランタイムの更新は env/src/native.rs、スロットと状態記録の操作は env/src/storage.rs に置く。 wasm 更新では、提出された内容を Secure 側のバッファへコピーしてから検証する(env/src/update.rs:131-172)。提出できるのは UPDATE 権利を持つドメインで、更新の対象はマニフェストのドメイン番号で決まる。検証・書込み・状態記録が成功すると、対象ドメインだけを新しい wasm で起動し直す(同 :207)。ネイティブ更新では大きなイメージを分割して転送し、完成したスロットのハッシュを確認してから、署名付きの入口で一つずつ起動し直す。選択済みの wasm は独立ストアに保持し、ランタイムに埋め込まれた別の wasm へ勝手に切り替えない。36 37 確定は、生存確認を受ける env/src/sched.rs:471 の heartbeat で行う。初期化完了と、実行中のドメインの版が試行中の版と一致することを確かめ、wasm の更新を確定する。ネイティブ更新では実際の文脈のスロットも照合し、次のドメインへ進むか全体を確定する。提出の時点では、HEARTBEAT 権利の無い版を -7 で拒否し、別の試行の途中なら -8 を返して出し直しを求める(env/src/update.rs:174-200)。古い版の拒否(-5)と区別するので、システムドメインは -8 のときだけ同じ更新を次の step で出し直す。30 36 確定済み版、試行版、使うスロットは、Secure 側が管理する状態記録に残す。この記録をジャーナルと呼ぶ。二つの保存面を交互に使う構成だが、AN521 の実装では保存領域に RAM を使う。QEMU プロセスを終了して作り直した場合の永続性は保証しない。両面とも未使用の値なら初期状態から始める。非空の記録が両面とも無効なら、下限を初期化せず停止する(env/policy/src/update.rs:140)。QEMU を起動し直すと保存用 RAM 自体が初期化され、版の下限も初期値に戻る。43 ④ 復旧の判断と、実際の再起動を分ける env/policy/src/recovery.rs は、異常回数と時間から再起動か隔離かを判断する。env/src/sched.rs:450 の apply_action は、その判断に従って再起動を予約するか、RAM を消去してマニフェストを外す。マニフェストを外すことで、権利も取り消す。29 30 1 tick は Secure タイマーの割り込み 1 回分で、AN521 の設定では 20,000 サイクルごとに来る(platform/board/an521/mod.rs の SLICE_CYCLES)。生存確認を要求するドメインでは、30 tick の途絶を監視する。健全期間が 30 tick 続くと検知回数をリセットする。native 更新の検証・書込みで Secure 側が専有した時間は、生存確認の監視時計から除く。NS が実行できなかった時間を、そのドメインの生存確認の途絶として数えないためで、Secure 処理の間も NS が動くという意味ではない。この時間は Secure 側のタイマーを基準とする PoC の設定であり、実機での正確なミリ秒値を確認したものではない。29 ⑤ 実行器が違っても、アプリとの契約を揃える 機器向けのランタイムは wasmi を使う。PC 上の sim では wasmi と Wasmtime を選べる。どちらも同じアプリ ABI を提供し、env/policy の検証・判断を使う。44 PC では、wasm の実行量を数える fuel を使って処理を区切る。QEMU では Secure タイマーによる実際の中断・再開を使う。方式が違うため、同じ時間特性を再現するものではない。通常のシナリオで比較するのは、実行順、アプリの結果、終了回数である。更新のシナリオでは、更新の受付・確定・復帰の判断を QEMU と PC で比べる。権利の拒否は、PC の単体試験と QEMU の観測で別々に確かめる。 動かすと何が起きるのか 動かす環境ごとの違い 環境 動かすもの 確かめること 残る確認 AN521 の QEMU Secure / NS の Cortex-M33 用ファームウェア MPU、割り込み、例外、wasm 実行、更新・復旧 RP2350 固有の動作、実機の時間・永続性 PC の机上シミュレータ 同じ wasm と、PC 用のサービス実装 ABI、権利、署名、更新判断、実行器間の差 ハードウェアの分離、実割り込み、実時間特性 RP2350 RP2350 用ファームウェア 確認したのはビルドまで 実機起動、保護違反、UART、フラッシュ更新、クロック hog が戻らなくても、ほかの処理へ進むか 通常のシナリオでは、dom0 に更新を受け取るシステムドメイン、dom1 に counter、dom2 に hog を置く。確認するのは、① hog が無限ループしても dom0 と dom1 に実行機会を渡すこと、② ランタイムの権利照合を迂回しても Secure 側が拒否することである。 このデモでは、ランタイムがアプリの step という入口を呼ぶ。counter は数を増やして呼出しから戻り、hog はその呼出しの中で無限ループする。CPU を割り当てる一回分がスライスであり、step が完了した回数とは異なる。40 31 まず見るのは、hog へ渡した後の切替である。保存した make qemu の出力から、この区間を抜き出す(保存した時点は後の「確認に使った環境と再現コマンド」に示す)。46 [S] slice 3 -> dom2 [S] slice 4 -> dom0 [NS->S] dom0 heartbeat [S] slice 5 -> dom1 [NS->S] dom1 log 2 [NS->S] dom1 heartbeat slice 3 で dom2 の hog に渡した後も、Secure 側は dom0、dom1 へ切り替えている。dom0 の生存確認と counter の log 2 が続くため、hog が自発的に戻るのを待たずに、ほかのドメインへ実行機会を渡せたことが分かる。生存確認は、アプリが実行を続けていると知らせる呼出しである。 次に、権利の拒否を見る。ここでは、wasm ではなく NS のネイティブコードに試験を置き、ランタイムが破られた場合の振る舞いを模している。dom0 は dom1 を名乗ってログを出し、dom1 はランタイムの照合を通さず UART を読もうとする。Secure 側が出す結果は次の二行である。 [S] denied log [S] denied UART dom0 にはログ出力の権利がなく、dom1 には UART の権利がない。Secure 側は名乗った番号を信用せず、実行中のドメインの権利で拒否した。実際の二行はそれぞれのドメインの初回実行で出ており、連続した出力ではない。試験は runtime/host/src/main.rs:42-48、判断は env/src/secure_api.rs:46 にある。28 31 起動時の署名・権利、終了回数、意図的な保護違反の観測 以下には、初期化の確認と、最後に意図して起こす保護違反も含む。中心の継続動作とは別の試験である。保存ログの時点と再検査の関係は、後の「確認に使った環境と再現コマンド」に示す。 [NS->S] NS running, CONTROL_NS.nPRIV=1 [NS->S] tag=30 val=1 [S] manifest dom0 v1 rights=14 verified [S] manifest dom1 v1 rights=3 verified [S] manifest dom2 v1 rights=1 verified (中略:初期化中の init tick 3 行) [S] domain initialization complete under MPU isolation [S] slice 1 -> dom0 [S] denied log [NS->S] dom0 heartbeat [S] slice 2 -> dom1 [S] denied UART [NS->S] dom1 log 1 [NS->S] dom1 heartbeat [S] slice 3 -> dom2 nPRIV=1 は、NS の非特権モードで動いていることを示す。tag=30 は、署名を 1 ビット壊したマニフェストを NS が提出し、Secure 側に拒否されたことを NS が報告した行である。dom0 の権利値 14 は生存確認・UART・更新、dom1 の値 3 はログ出力と生存確認、dom2 の値 1 はログ出力に対応する(env/policy/src/lib.rs:13-16)。dom1 に生存確認を与えるのは、更新した版を生存確認で確定させるためである(③)。 二つの denied の行は、wasm アプリではなく、ランタイムのネイティブコードに置いた試験の結果である。乗っ取られたランタイムの振る舞いを模して、dom0 の NS コードは dom1 の番号を名乗ってログを出し、dom1 の NS コードはランタイムの権利照合を通さずに Secure API で UART を読む。Secure 側は、どちらも実行中のドメインの権利で判断して拒否した。②の「呼出側が名乗った番号を信用しない」「ランタイムの照合を前提にしない」を確かめる観測である(試験は runtime/host/src/main.rs:42-48、判断は env/src/secure_api.rs:46)。 9 回の時間割当てを終えると、次のようになった。 [S] scheduler stopped after 9 slices [NS->S] dom0 steps 3 [NS->S] dom1 steps 3 [NS->S] dom2 steps 0 steps は、最後まで終了したアプリの呼出回数で、NS 側が数えて報告した参考値である。dom2 は呼出しから戻らないため 0 だが、dom0 と dom1 はそれぞれ 3 回終了している。 Secure 側が出した行でも同じことが分かる。全ログでは、dom2 への割当て(slice 3・6・9)のあとも dom0・dom1 への割当てが続き、Secure 側が実行中のドメインを判定して出した dom0 の生存確認と、dom1 の log 2・3 と生存確認が現れる。これが、アプリの自発的な終了に依存せず、ほかのドメインへ実行機会を渡せた観測になる。 最後に、観測用の起動文脈へ戻って、許可されていないアドレスを意図的に読む。この試験は次の出力で終わる。 [NS->S] reading 0x40000000, which MPU_NS does not allow [S] Secure HardFault: EXC_RETURN=0xffffffb9 from_ns=true [S] HFSR_S=0x40000000 HFSR_NS=0x40000000 (FORCED=bit30) [S] CFSR_NS=0x00000082 MMFAR_NS=0x40000000 (DACCVIOL=bit1 MMARVALID=bit7) 最後のフォールトは、保護違反が Secure 側へ届くことを確かめる意図した終了である。通常のドメインの異常を検知し、再起動や隔離をする④の試験とは区別する。46 同じ通常シナリオについて、QEMU、PC の wasmi、PC の Wasmtime から比較対象の 22 行を取り出す試験も再実行し、一致を確認した。22 行には dom1 の生存確認の 3 行を含む。アプリの結果だけでなく、実行順と終了回数を比較する試験であり、全ログや機器の時間性能の一致を要求するものではない。denied の行は比較対象に含まない。47 python3 tests/parity.py の出力は次のとおりである。 QEMU / wasmi / wasmtime: 22 lines identical counter だけを更新し、失敗したら戻せるか 一般アプリの個別更新では、counter の新版 v2 を採用し、生存確認を送らない v3 なら v2 へ戻す。その間も dom0 が続くことを見る。受付時の拒否も確かめるため、次の四つを UART から順に送る。48 送る順 counter の版 中身と権利 確かめること 1 v4 LOG のみ。生存確認の権利がない counter 試行へ入る前に拒否する 2 v2 LOG と生存確認を持つ counter 対象だけを試し、生存確認で確定する 3 v1 LOG と生存確認を持つ counter 確定した下限より古い版を拒否する 4 v3 LOG と生存確認の権利はあるが、生存確認を送らない hog 試行後に v2 へ戻す ここでの v 番号は、dom1 に配る wasm の版を表す。v4・v2・v1 は、最初から動いているものと同じ counter.wasm に版と権利を付けて署名しており、計算処理は変わらない。v3 は dom1 に hog を入れる試験で、通常シナリオの dom2 にいる hog とは別の更新対象である。どの版が起動したかは、Secure 側の restart dom1 vN で読む。 2026-10-20 に python3 tests/app_update.py を実行すると、次の出力が得られた。UART の受信速度は QEMU を動かすホストによって変わるため、再実行すると slice 番号は変わる。判断の順序は変わらない。 [S] slice 46 -> dom0 [NS->S] dom0 heartbeat [S] update dom1 rejected heartbeat required v4 (中略) [S] slice 190 -> dom0 [NS->S] dom0 heartbeat [S] update dom1 trial v2 [S] restart dom1 v2 RAM cleared [S] slice 191 -> dom1 [S] slice 192 -> dom2 [S] slice 193 -> dom0 [S] slice 194 -> dom1 [S] denied UART [NS->S] dom1 log 1 [S] update dom1 confirmed floor=2 [NS->S] dom1 heartbeat (中略) [S] update dom1 rejected rollback/busy v1 (中略) [S] update dom1 trial v3 [S] restart dom1 v3 RAM cleared (中略:dom0 の生存確認が続き、dom1 の v3 は生存確認を送らない) [S] slice 931 -> dom0 [NS->S] dom0 heartbeat [S] update dom1 reverted floor=2 [S] restart dom1 v2 RAM cleared 順に読むと次のようになる。 生存確認の権利を持たない v4 は、書き込む前に拒否された。この版は生存確認を送れず、確定できないためである。 v2 は dom1 だけを起動し直した。log 1 から数え直しているのは、dom1 の RAM を消去して新しい版を起動したためである。v2 が最初の step で生存確認を送った時点で、Secure 側が確定し、版の下限が 2 に進んだ。v2 の権利には UPDATE が無い。一般アプリに更新の権利を与えずに確定できたことになる。 下限より古い v1 は拒否された。 v3 は試行に入ったが、生存確認が途絶したため、確定済みの v2 へ戻った。counter の更新に失敗しても、戻すのは dom1 だけである。 この間も dom0 の生存確認が続いている。denied UART は、通常シナリオと同じ NS コードの権利試験が dom1 の再起動時にも動いた結果であり、更新失敗を示す行ではない。 復帰の期限とスライス数 v3 は試行開始から 33 スライス後に戻った。生存確認の途絶を見る期限(30 tick)が、試行期限(90 tick)より先に切れたためである。tick は Secure タイマーの割り込みを数える単位で、スライスとは数え方が異なる。初期化中も tick は進むが、slice 番号は進まず、Secure 側の処理中に見送った tick もあるため、両者の差分は必ずしも一致しない。 同じ UART 入力を PC の wasmi と Wasmtime にも与え、更新の判断 6 件が QEMU と一致することも確かめた。 QEMU / wasmi: 6 app update decisions identical QEMU / wasmtime: 6 app update decisions identical この観測から言えるのは、一般アプリだけを試行に入れ、試行中の版の生存確認で確定し、確定しなければそのアプリだけを戻せることである。確定が示すのは生存確認を送れたことまでで、正しく動いていることの証明ではない。 システムドメインの更新、初期化中に生存確認を送ってから失敗する版の復帰、異常を三回検知した後の隔離も、ランタイムの順次移行を実装した後の make check で通った。通常22行と、wasm 更新3種類の判断各6件が QEMU・wasmi・Wasmtime で一致した。49 共有ランタイムの更新は、何が違うか ここからの v 番号は、アプリではなくランタイムの版を表す。この試験では、dom2 の通常の hog を、生存確認を送るアプリへ置き換える。すべての対象の生存確認が必要なため、通常の hog を含む構成のままでは更新を開始できない。 make check-native では、実際の runtime/host v2 を UART で送り、dom0→dom1→dom2 の順に移行した。UART で送る量は全体で約 506 KB になる。49 版の下限は、最後の対象が終わったときだけ 2 へ進んだ。続く v3/v4 の失敗試験では、新版へ移した dom0 と dom1 を、直前に確定した v2 で起動し直した。失敗側には小さな署名付き試験用イメージを使い、実ランタイムの失敗版を転送したものではない。一般アプリ更新と違い、失敗時に戻す対象は一つとは限らない。50 ランタイム更新の試験内訳と未確認の条件 ランタイムの更新(make check-native)では、実際の runtime/host v2 を UART で送り、dom0→dom1→dom2 の順に移行し、最後だけ版の下限が2へ進むことを確かめた。続けて旧版v1を拒否し、v3ではdom0移行後にdom1の生存確認を途絶させ、v4では同じ位置で故障させた。いずれも新版を使ったdom0とdom1がv2へ戻り、旧スロットも再利用できた。v3/v4は小さな署名付き試験用イメージであり、実runtime/hostの失敗版を転送したものではない。カウンタの連続性を含め26判断が通った。別の小さい更新を使う4試験では、生存確認の権利が無い構成の開始拒否、移行後に旧bootへ戻らずSecure側で終了すること、新旧スロット相互のコード実行拒否を確認した。50 51 この観測には試験用イメージで起こした失敗も含まれ、実ランタイムのあらゆる失敗を確認したものではない。また、次の条件は動的な確認が残る。 旧形式(format=2)の確定済みネイティブイメージからの実起動。旧形式のイメージを起動する互換経路は、今回の動的試験では未確認である。 ランタイム内に埋め込む wasm を変更したネイティブイメージへの更新後も、独立ストアで選択した wasm を維持すること。実装はこの維持を意図するが、その条件の動的確認は残る。 設定方法 以下は、設定を変更したり、観測を再現したりするための参照資料である。全体像を把握する場合は、次の「どこまでを保証の対象にするのか」へ進める。 五つの機能に対応する設定の入口 現在の設定は、汎用の設定ファイル一つに集まっていない。Makefile の変数、Rust の定数、署名するパッケージの生成処理に分かれている。変更できる値と、変更後に再確認すべき条件を分けて読む。 機能 設定の入口 現在の値・選択肢 変更時の条件 ① メモリ分離と実行時間の割当て env/policy/src/lib.rs:7、platform 3 ドメイン、各 RAM 112 KiB、うちスタック 16 KiB。機種ごとの時間割当て定数 ドメイン数はアプリ配列・配置なども合わせる。RAM とタイマーの成立を再検証する ② 権利の強制と UART 処理 sim/src/bin/package.rs:33 システムに HEARTBEAT・UART・UPDATE、counter に LOG・HEARTBEAT、hog に LOG パッケージを作り直して署名する。権利を増やすと、そのドメインが悪用できる範囲も広がる ③ アプリ・ランタイムの更新と復帰 env/policy/src/lib.rs:173、env/src/update.rs:206、platform 更新 wasm 上限 8 KiB、wasm 試行期限 90 tick、ネイティブスロット各 1 MiB 転送バッファ・スロット・リンク配置と、試行期限を揃えて検証する ④ 異常検知・再起動・隔離 env/policy/src/recovery.rs:2 生存確認期限・健全期間が各 30 tick、3 回で隔離 判定はソース変更と再ビルドが必要。実時間への換算は機種のクロックに依存する ⑤ QEMU と PC で同じ wasm を検証 Makefile:10 PLATFORM=rp2350/an521、RUNTIME=wasmi/wasmtime、PROBE qemu は AN521 を選ぶ。RUNTIME が切り替えるのは PC の sim 側だけ ①のサイズは、次のビルド時定数から始まる。ドメイン数だけを変更すれば全体が自動で拡張する設定ではなく、現在はアプリ配列などにも 3 ドメインを前提とする箇所がある。31 env/policy/src/lib.rs:7: pub const DOMAINS: usize = 3; pub const RAM_SIZE: u32 = 112 * 1024; pub const STACK_SIZE: u32 = 16 * 1024; ②の権利は、ビルド成果物へ署名する処理が選ぶ。現在のデモでは、先頭の wasm をシステムドメインにする。更新して確定させるアプリには生存確認(HEARTBEAT)を与え、戻らない hog には与えない。一般アプリ側にも UART や更新の提出を許可するなら、このポリシーを変更する必要がある。52 sim/src/bin/package.rs:33-38: let rights = match id { 0 => HEARTBEAT | UART | UPDATE, 1 => LOG | HEARTBEAT, _ => LOG, }; 生存確認を与えたドメインは、途絶の監視と、途絶したときの再起動の対象にもなる(④)。共有ランタイム更新では全対象に生存確認が必要なので、前述の更新試験では dom2 にも生存確認するアプリを用意している。 署名用の開発鍵は make keys がローカルで生成し、既存の鍵を再生成しない。Secure 側には公開部分を組み込む。製品向けの鍵配布・ローテーションを実装した仕組みではない。53 54 確認に使った環境と再現コマンド 本文が説明するソースは 2026-10-20 時点のもので、検証日も同日である。掲載した保存ログと、その後の再検査を次のように区別する。 証跡 時点 本文で使う範囲 通常実行・一般アプリ更新の保存ログ 生存確認による確定を実装した時点、2026-10-20 掲載したログの抜粋と、22行・更新判断6件の比較 基準ソースでの再検査 ドメインごとのランタイム移行を実装した後、同日 make check の通過、実ランタイム更新の追加観測 ファームウェアは nightly-2025-07-15 でビルドし、thumbv8m.main-none-eabi と wasm32-unknown-unknown をターゲットにする。PC の sim は stable を使う。QEMU 実行には qemu-system-arm とログ復号用の defmt-print、補助スクリプトには Python 3 と make が必要になる。UART 入力を伴う試験には、スクリプトが使う file chardev の input-path に対応する QEMU 9.1 以降が必要である。46 55 49 保存ログと再検査で使ったツールの版 ツール 保存ログの実行時 ランタイム移行の実装後の検査 QEMU 10.2.1 10.0.13 ファームウェア用 rustc 1.90.0-nightly 同じ nightly PC 用 stable rustc 1.91.1 1.99.1 Python 3.14.4 3.12.14 比較するパッケージの出典は版やタグを固定している。Ocre はリリースタグではなく、main の commit c21cc29 を基準とした。 以下はリポジトリ直下から実行する。 make qemu make sim RUNTIME=wasmi make sim RUNTIME=wasmtime python3 tests/parity.py python3 tests/app_update.py ビルドは鍵生成と wasm のパッケージ生成も行う。初回には、rustup で上記ツールチェーンと二つのターゲットを用意しておく。コードに固定した版と依存関係は rust-toolchain.toml、Cargo.lock、sim/Cargo.lock で確認する。55 追加の検証入口は次のとおりである。49 make check make check-native make build PLATFORM=rp2350 make check は整形、ポリシーテスト、sim テスト、ログ検査の単体試験、開発鍵の検査、QEMU と sim の比較、UART、システムドメインと一般アプリの wasm 更新、復旧、RP2350 ビルドを実行する。時間のかかるネイティブ更新は make check-native に分けている。 どこまでを保証の対象にするのか タイトルの「セキュアに」は、次の脅威モデルの範囲を指す。悪意ある wasm アプリと、欠陥を突かれて任意のコードを実行するランタイムを攻撃者として扱う。保護の成立には、Secure 側の実装、CPU と SoC の保護機構、初期のコードと鍵が正しく用意されることを前提とする。物理攻撃、TCB 自身の欠陥、ビルド環境や署名鍵の侵害は対象外である。 また、現 PoC にはアテステーション、鍵サービス、PSA API、Secure 側自身の更新、ドメイン間通信は含めていない。起動時に wasm の署名を検証することを、機器全体の製品向けセキュアブートが完成していることと混同しない。 版の下限による巻戻し防止は、下限が電源断や再起動をまたいで残ることが成立条件になる。AN521 の QEMU では保存領域が RAM なので、QEMU を起動し直すと下限も初期値に戻る。実機では消えない保存領域が要る。 RP2350 は実機未観測であり、UART の受信限界、実クロックに基づく時間、フラッシュ操作、電源断を含む復旧は、実機での確認が残る。QEMU と sim の結果を、そのまま実機での保証に読み替えることはできない。 この先、何を足せるのか ここでは、現 PoC に無い機能のうち、拡張の候補を挙げる。いずれも未実装の構想であり、動作は確かめていない。前の章で範囲外とした項目も、この中で扱う。 どの候補も、Secure 側に足すか NS 側に置くかで性格が変わる。Secure 側に足すほど、正しさを信頼する範囲(TCB)が広がる。TCB の肥大化は、この構成で継続して監視すべき欠点である。以下では、各候補が現 PoC のどの制約から出てくるかを示す。 拡張候補とその成立条件 実行環境の機能を広げる ドメイン間通信:現 PoC のドメインは互いのメモリに届かず、データを渡す手段も持たない。センサーの読取りと送信を別のアプリに分けるには、ドメインの間でデータを渡す経路が要る。分離を崩さないよう、送り手と受け手の権利を Secure 側が照合する形が候補になる。 スケジューリング方式の選択:現在は固定順のラウンドロビンだけで、優先度や重み付けは無い。30 応答を急ぐ処理を持つ機器では、方式を選べるようにしたい。ただし、スケジューラは TCB に入るため、方式を増やすほど確かめる対象も増える。選べる方式を絞り、どれも Secure 側で検証できる形にする必要がある。 鍵管理:現在の鍵は開発用で、製品向けの配布・ローテーションは無い。53 アプリが鍵そのものに触れずに署名や暗号化を頼める鍵サービスを、Secure 側に置くことが候補になる。API を PSA Crypto の形にそろえれば、TF-M 向けに書かれた呼出し側とつながる。4 アテステーション:構想では、実行内容の証明を TCB の役割に含めている。Secure 側が、確定した版とマニフェストのハッシュを署名して機器外へ示せれば、更新が届いたことをサーバー側で確かめられる。 周辺機器アクセスの締め付け:UART の権利を持つドメインには、UART のレジスタへの直接アクセスも開いている。39 42 設定の変更を Secure 側の API に限れば、権利を持つドメインが乗っ取られたときの影響を狭められる。受信の遅れが問題になれば、割り込みの受付部を NS 側に置く方式も候補になる。 通信路の拡張:現在の更新経路は UART だけである。USB や Ethernet、無線の処理を、システムドメインの wasm として足すことが候補になる。外部入力を NS 側で解析する方針は変えない。 データの永続化:版の下限は、消えない保存領域に置かなければ巻戻し防止が成り立たない。実機での永続化に加え、アプリごとに分けた保存領域を権利付きで提供すれば、アプリの設定や記録も更新後に引き継げる。 Secure 側自身の更新:env 自体は、現在の更新経路の対象外である。Secure 側の欠陥を直すには、起動時の検証と、失敗時に戻る復旧用イメージを含めた別の更新経路が要る。 異常の兆候を見つけて止める ④の検知は、フォールトと生存確認の途絶の二つに限られる。次の三つは、その検知を広げる候補である。止める処理には、④の再起動・隔離を使う。 アプリドメインの監視:生存確認が示すのは送れたことまでで、生存確認を送り続ける異常は検知できない。ドメインごとの CPU 時間の使い方、Secure API の呼出しの頻度、権利の拒否やフォールトの回数を Secure 側で記録し、機器外へ報告できれば、異常の兆候を早く捉えられる。 割り込みの監視:Secure 側が受けるのは、フォールト、時間の割当てに使うタイマー、UART の割り込みだけである。その回数や処理時間は数えていない。35 外部から UART へ大量に送りつけられると、Secure 側の割り込み処理が続き、どのドメインの実行時間にも影響しうる。割り込み源ごとに回数と処理時間を記録し、異常に多ければその源を一時的に抑えることが候補になる。また、想定外の例外を受けると、現在はシステム全体を止める。35 原因のドメインを特定できる場合は、そのドメインだけの復旧に寄せたい。 定義ファイルによる検知:PC のウイルス対策ソフトが定義ファイルを外部から受け取るように、不審な振る舞いの定義を機器外から受け取り、似た振る舞いを見つけたらそのドメインを止める。定義ファイルを更新と同じ経路で受け取れば、Secure 側が署名と版を検証し、古い定義への巻戻しも防げる。照合の処理は Secure 側に置き、定義は固定形式の表に限る。表に書くのは、既知の不正な版のハッシュと、境界で起きる出来事(Secure API の呼出しの頻度、権利の拒否やフォールトの回数など)に対する閾値と処置である。自由な形式の定義を Secure 側で解析すると、外部入力を NS 側で解析する方針に反し、TCB も広がる。監視用のドメインを NS 側に置いて Secure 側へ停止を求める形も考えられるが、その監視ドメインが乗っ取られると、ほかのドメインを止められてしまう。 定義ファイルによる検知には限界もある。Secure 側から見えるのは、境界を越える出来事と wasm のイメージだけで、ランタイムの中の計算は見えない。乗っ取られたランタイムが境界での振る舞いを正常に見せかければ、検知できない。誤検知すれば、正しいアプリも止まる。 構成とハードウェアを広げる マルチコア対応:現 PoC は、一つのコアでドメインを順に切り替える。30 コアが複数あれば、ドメインを同時に動かせる。その場合、Secure 側は、コアごとの実行中ドメイン、メモリ保護の設定、時間の割当てを管理する必要がある。コアごとに保護設定をどう持つかは、対象の SoC で確かめる必要がある。 コアごとの役割を選べる構成:現在は、ドメイン数や RAM の大きさがビルド時の定数とアプリ配列などに分かれて決まり、コアの使い方も一通りしかない。どのコアに何を置き、ドメインに何を割り当てるかを一か所で宣言できれば、同じ実装から機器に合った構成を選べる。たとえば次の三つの構成が考えられる。 一方のコアを env 専用にし、もう一方のコアで runtime を動かす 一方のコアは今と同じく env と runtime を時分割で動かし、もう一方のコアで runtime だけを動かす 一つのコアで env と runtime を時分割で動かす(現 PoC) 非TrustZone搭載Cortex-MとTrustZone搭載Cortex-M混在のヘテロ構成において前者をruntimeのアプリドメイン専用にして後者にその他を搭載して動かす 開発環境を広げる 上の機能を機器に足すときは、⑤の机上シミュレータにも同じ契約を足す必要がある。ドメイン間通信や鍵サービスをアプリ ABI に加えた場合、PC でも同じ関数を提供しなければ、配布するものと同じ wasm を PC で試せなくなる。拡張のたびに、機器と PC の比較シナリオも増やす。 まとめ tRustedVisor-M は、Cortex-M の TrustZone-M を土台に、IoT 機器のアプリの分離・個別更新・検証を支える、Rust 実装の開発環境である。中心の問いは、TrustZone-M を備える Cortex-M の MCU で、アプリを分離して動かし、言語に縛られずに個別更新・検証するには、何を誰に任せればよいか、だった。 既存のパッケージも、イメージの更新、拡張の読込み、wasm の配布などに答えている。tRustedVisor-M の PoC がそろえたのは、アプリ単位の配布に加え、ランタイムの欠陥を想定した分離と、同じ単位での検証・復帰である。そのために、次のように分担した。 NS 側の非特権のドメインが、アプリを実行し、外部から届いたデータを解析する。ランタイムもここに置き、信頼の前提から外す。 Secure 側が、メモリと時間の分離、権利の照合、更新の検証・試行・確定・復帰、異常時の復旧を判断する。 PC が、配布するものと同じ wasm を動かし、アプリの結果とサービスの判断を比較する。 ハイパーバイザーの分離・資源管理の役割を超えて、更新・復旧・配布前の検証までを扱うことが、この構想の狙いである。 QEMU では、hog が戻らなくても counter とシステムドメインに実行機会を渡し、権利のない呼出しを拒否できた。counter だけの更新を生存確認で確定し、生存確認を送らない版ならそのドメインだけを元へ戻せた。これが、冒頭の三つの処理で確かめた結果である。 共有ランタイムも順次移行できたが、途中で失敗すれば移行済みの対象も旧版で起動し直す。Secure 側の処理中には NS 側の実行が待たされる。また、QEMU の版の下限はプロセスを起動し直すと初期値に戻る。実機、電源断を含む復旧、ランタイム更新の一部の互換条件は未確認である。 QEMU と PC の結果が一致したのは、実施したシナリオの範囲である。実機の動作、アプリの正しさ、任意の攻撃に対する安全性まで証明したものではない。

  3. 项目从一个传感器长成一堆外设时,可以看看 Zephyr。它是面向资源受限设备的 RTOS,支持多种处理器架构和开发板。 适合系统地学嵌入式工程。第一步选一块明确支持的板子,把官方示例跑通。 zephyrproject-rtos/zephyr t.co

    1 like · 17 views
  4. Reddit · · r/embedded · u/Commercial_Froyo_247

    ESP-Hosted-MCU on Zephyr with an STM32 host: my numbers + lessons learned Hi everyone, Lazy Monday morning post, at least in my time zone :) I haven't seen much information online about using ESP-Hosted-MCU with a non-ESP host. For those who don't know, it's firmware from Espressif that turns your ESP32 into a Wi-Fi coprocessor. You connect it to your microcontroller over SPI or SDIO, and the TCP/IP stack runs on the host. About numbers, the only thing I found was a question here on r/embedded asking whether the 22 Mbps from the README is real. It has two comments, but nobody posted any measurements. So today I want to share my results with this firmware, measured in my use case and with my topology. My setup and how I measured Device. It's a 4-channel Matter relay for the lights in my house. It works over Wi-Fi or Ethernet and also measures energy consumption. I use it with Apple Home and SmartThings. Live and neutral go straight to WAGO terminals, so you don't need extra wires in the distribution box, you just connect it and forget it. Host. The main controller is an STM32U585. Code runs XIP from QSPI NOR flash, and there is also 8 MB of QPI PSRAM. Both memories use the same physical QSPI bus: data lines and clock are shared through the OCTOSPI I/O manager, and each memory has its own chip select. Wi-Fi. The coprocessor is an ESP32-C6-MINI-1U with ESP-Hosted-MCU 3.0.6. It shares the SPI bus with a W5500 Ethernet chip, and the bus runs at 40 MHz. OS. Zephyr, main from late August 2026 (4.4.99), with the upstream esp_hosted driver and a few local patches, more on them later. Measurements. I measured with zperf on the board and a Mac on the same network. For UDP the board transmits and I look for the ceiling, with 1000-byte datagrams for 10 s, and I count throughput from the bytes that actually reached the Mac. For TCP the board receives. During each test the other interface is turned off, so the shared SPI bus belongs fully to the one I'm measuring. Real load. I also didn't want synthetic numbers on an idle CPU, so I measured on the real device with Matter running and all controller subscriptions active. So these are numbers for a real application, not the hardware maximum. Wi-Fi is 2.4 GHz, 802.11ax, RSSI -57 dBm. Results, Mbps |Type|PSRAM, no DMA|SRAM, no DMA|SRAM + DMA|PSRAM + DMA + patches| |:-|:-|:-|:-|:-| |TCP RX, Ethernet|1.41-1.49|1.52-1.63|1.81-1.82|1.40-1.47| |TCP RX, Wi-Fi|0.89-1.06|0.64-0.77|1.84-2.27|1.64-1.65| |UDP TX (ceiling), Ethernet|2.07|2.15|3.16-3.17|2.48-2.49| |UDP TX (ceiling), Wi-Fi|1.47|1.66|3.72-3.76|2.25-2.26| |HTTP (115 KB gzip), Eth / Wi-Fi|164 / 83 KB/s|-|-|178 / 105 KB/s| Enough for Matter. Despite the modest numbers, this is fully enough for a Matter device connected to several Matter controllers. Matter only needs kilobits per second, and the web page opens fast over both Wi-Fi and Ethernet (first byte over Wi-Fi in 75 ms). The 22 Mbps from the README. The table that listed 22 Mbps TCP for standard SPI didn't say what the test conditions were (and it's no longer there). In my case the bus is shared with Ethernet, and the CPU is busy with Matter at the same time. A faster host will do better, but for serious speeds it's better to use SDIO rather than SPI. How I got to these numbers The way there was not straight. No DMA. I started with the simplest setup, with network buffers in PSRAM and SPI in polling mode. Both interfaces topped out at 1.5-2 Mbps, and the CPU was 100% busy during transfers, it was just moving bytes through SPI. SRAM instead of PSRAM. My first guess was that slow PSRAM was the problem, so I moved the buffers to SRAM. It gave only +4-13%, and TCP over Wi-Fi even got worse. And it cost 200 KB of SRAM, which is a lot on my board, because Matter and crypto already take most of it. So the memory itself was not the problem. DMA. Then I noticed that the board's devicetree had no DMA for this SPI bus at all. Once I added it, it turned out to be the biggest win. Wi-Fi UDP went from 1.66 to 3.76 Mbps, and Wi-Fi TCP almost tripled. Wi-Fi even overtook Ethernet, which I didn't expect. Back to PSRAM. But I still didn't want to give up 200 KB of SRAM, so I wanted the buffers back in PSRAM, this time with DMA. The stock SPI driver doesn't do DMA to cacheable memory, and PSRAM without the data cache doesn't make much sense, so I added cache maintenance to the driver. Cache driver bug. Under load the board crashed with a corrupted semaphore wait queue. The problem turned out to be in the STM32 cache driver: range operations touched one extra cache line and overwrote a neighboring semaphore. A dedicated test confirmed it (1000 failures out of 1000 without the fix, 0 with it), and the fix is already in Zephyr. FIFO overrun. The next problem was that Wi-Fi lost a whole second on every SPI error. Every now and then I got spi_transceive failed, and the bus stalled for a second. I read the SPI status register with a debugger right on the running board, and all 19 errors out of 19 were RX FIFO overruns. DMA can't move the bytes to PSRAM fast enough, the FIFO fills up, and SCK keeps clocking. New bytes are lost, the DMA channel waits for them forever, and the driver silently gives up on a one-second timeout. Lower clock? At 20 MHz there were 10 times fewer errors, but that only hides the problem. The W5500 hits the same thing, and its driver is silent too. So until DMA has drained the FIFO, the master has to stop SCK instead of clocking into a full buffer. MASRX. The STM32U5 SPI has a mode for exactly this problem, called MASRX. When the RX FIFO is full, the master suspends SCK by itself, and the driver has to resume the transfer. I turned it on by hand. The errors went away, but a minute later the board hung. The Zephyr driver knows nothing about this pause, so nobody was releasing it. So I made two driver changes. First fix. The driver now catches the overrun with an interrupt and returns an error within milliseconds instead of a second. This way the error shows up right away instead of after a one-second wait, and this alone made TCP over Wi-Fi 2-3x faster. Second fix. The driver enables MASRX and releases the pause, but only after DMA has taken the data out of the FIFO. At first I released the pause right away, and the next frame overflowed the FIFO again. The pause is released from the interrupt, but I didn't want the interrupt to spin for long, so if DMA hasn't drained the FIFO within 5 µs, the thread waiting for the end of the transfer releases it instead. With both changes I get 0 SPI errors and 0 UDP loss, that's the right column of the table. Smaller things. Network buffers had to be aligned to the cache line (CONFIG_NET_BUF_ALIGNMENT=16), otherwise the invalidate after RX can hit a neighboring buffer. And bigger buffers (512 bytes instead of 256) gave only +2-12%, so I stayed with 256. Where the patches are now Cache fix. The STM32 cache driver bug is already fixed in Zephyr. SPI driver. To make DMA work with buffers in external memory, I made a fix in the STM32 SPI driver, and the overrun and MASRX changes are in it too. Here is the pull request: zephyr#120895 Review status. So far I've only tested it on STM32U5, which is why it's only enabled there. The ST maintainers asked me not to tie the code itself to U5. I've reworked that and now I'm waiting for another review. Until it's merged, the stock driver falls back to interrupts or polling for such buffers. Why I'm posting this. Partly so that if someone runs into the same problems, they don't have to go through all of this from scratch. Testers wanted. If anyone wants to try the driver on their own boards, I'd really appreciate tests, feedback and advice. Data cache on STM32U5. One more thing if you test with external memory. Zephyr still doesn't enable the DCACHE1 data cache there by itself, only the instruction cache is turned on at startup (on H5, for example, both are). At the same time CONFIG_DCACHE=y is the default, so drivers treat external memory as cacheable even though the cache is physically off. So turn the data cache on manually. Buffer alignment. The cache maintenance for DMA buffers (flush before TX, invalidate after RX) is done by the SPI driver with my change. The application only needs one thing: the RX buffer must start on a cache line boundary, and its length must be a multiple of the cache line. Otherwise the driver sends it the old way, without DMA, so it doesn't touch neighboring data. For network buffers that's the same CONFIG_NET_BUF_ALIGNMENT=16. A few more technical details CPU load. Who eats the CPU with DMA? I expected DMA to offload the core, but that did not really happen. In the final configuration the CPU is idle only 0-5% of the time during transfers. At the same 100% load, the CPU just manages to move more data. On Ethernet most of the time goes to the network stack's TX queue thread (~56% of transfer time) and the W5500 driver thread (~15%). On Wi-Fi it's the esp_hosted thread (~45%) and the same TX queue (~25%). Matter and everything else take a few percent. Shared bus. Does the shared flash and PSRAM bus get in the way? I think partly yes. With the CPU equally 100% busy, each byte costs more cycles with buffers in PSRAM than with buffers in SRAM: 1.3x on Ethernet and 1.7x on Wi-Fi. That's why PSRAM + DMA doesn't reach SRAM + DMA (3.16 / 3.76). I can't say what that cost consists of. It could be waiting on the shared QSPI bus (code runs from the flash on the same bus), cache maintenance around DMA, or something else. Thread statistics don't show it, and I don't have a bus profiler. Firmware cost. ESP-Hosted takes ~25 KB of code and ~58 KB of RAM, plus 8 KB for esp-serial-flasher, so the C6 can be flashed directly through the STM32. Boot to the first HTTP response takes 13-16 s over Ethernet and 19-28 s over Wi-Fi. Has anyone else measured ESP-Hosted with a non-ESP host, over SPI or SDIO? What numbers did you get? And if you try the driver on your board, let me know how it went. Thanks for reading! Now you can tell me I should have just used an ESP32-P4 :) P.S. The photo shows an old revision of the board. In the new one I moved the mains traces from the top layer to one of the inner layers, and the fuse to the bottom layer, so you can't touch them by accident. And this is just a home project, not for sale. P.P.S. Questions are welcome in English, Ukrainian or Russian.

    197 points · 33 comments
  5. Reddit · · r/BestGitHubRepos · u/company_url_finder

    Zephyr: a new generation, scalable, optimized, secure RTOS for multiple hardware architectures (16.8k stars) What it does All text below is quoted directly from the project README. >The Zephyr Project is a scalable real-time operating system (RTOS) supporting multiple hardware architectures, optimized for resource constrained devices, and built with security in mind. Introduction: - The Zephyr OS is based on a small-footprint kernel designed for use on resource-constrained systems: from simple embedded environmental sensors and LED wearables to sophisticated smart watches and IoT wireless gateways. - The Zephyr kernel supports multiple architectures, including ARM (Cortex-A, Cortex-R, Cortex-M), Intel x86, ARC, Tensilica Xtensa, and RISC-V, SPARC, MIPS, and a large number of supported boards. - Welcome to Zephyr! See the Introduction to Zephyr for a high-level overview, and the documentation's Getting Started Guide to start developing. - Community support is provided via mailing lists and Discord; see the Resources below for details. - Discord Server for real-time community discussions - Report security vulnerabilities at [vulnerabilities@zephyrproject.org](mailto:vulnerabilities@zephyrproject.org) Repository zephyrproject-rtos/zephyr Repository stats |Metric|Value| |:-|:-| |Stars|16,804| |Forks|10,184| |Last release|v4.4.2 (2026-08-07)| |Languages|C 90.3%, Python 4.7%, CMake 2.7%| |Contributors|3,669| |AI contributor accounts (Claude / Codex / Cursor / Copilot)|claude: 31| |AI co-authored commits|Claude: 36, Cursor: 5, Copilot: 6| Main contributor |Field|Value| |:-|:-| |Name|Anas Nashif| |GitHub|@nashif| |Commits to this repo|7,085 (top contributor)| |Company / Location|Intel / Montreal, Canada| |Followers|307| |Public repos|99| |On GitHub since|2010-01-11|

    3 points
  6. Zephyr に送った PR がはじめてマージされた。 zephyr#120335 はじめてのマージおめでとう🎉 ってボットに言われて嬉しくなってる。

    8 likes · 1 repost · 349 views
  7. Zephyr 4.2.0 NVS: for len > 0, nvs_write() returns 0 if the ID already holds identical bytes. Treat 0 or len as success and negative values as errors. A loop waiting only for rc == len can retry an unchanged value forever. github.com/zephyrproject-rtos/zephyr/blob/413b789… t.co

    1 like · 32 views
  8. Zephyr 4.2.0: submit the same idle, initialized item three times before dequeue: k_work_submit() returns 1, 0, 0 on an accepting queue. Store events separately and check submit returns for errors (<0). A running item can be queued again. github.com/zephyrproject-rtos/zephyr/blob/413b789… t.co

    17 views

Latest stargazers

The last 10 distinct accounts we observed this year.

Most active participants

PR, issue, comment & review events in the past year.

Packages

2
PackageVersionDownloads · 30dTrend · 30d
———
———

README badges

Embed a badge in your README or website.

  • zephyrproject-rtos/zephyr reached #2 in daily PR authors on gitwal · Feb 7, 2019
Live mentions · last 30 days
zephyrproject-rtos/zephyr mentions in the last 30 days