ブートローダとOTA更新
現場に出た機器のファームを、電源が抜けても文鎮化させずに無線更新する仕組みが分かる。多段ブート・A/Bバンク・署名検証・原子的な切替という4つの原理を、なぜそうするかから理解できる。
- 1.組込みの起動は多段:ROMブート→1次ブートローダ→2次ブートローダ→アプリと権限を渡し、各段が次段を検証してから制御を移す(Secure Boot の信頼の連鎖)。
- 2.OTAは新旧2つの領域を使い分けて更新する。A/Bバンクは無停止で書け、単一バンク+リカバリは容量を節約する。切替は1ビットの原子的な書換えで確定させる。
- 3.電源断に耐える鍵は『更新中は現用面に触れない』こと。新面へ書き切り→署名検証→ブート順の切替→試験起動→確定(commit)の順で、どこで落ちても旧面へ戻れるよう設計する。
電源を入れてからアプリが動くまで
組込み機器の電源を入れた瞬間、CPU はまだ自分のフラッシュに何が書かれているかを知りません。最初に実行されるのは、シリコンに焼き込まれた変更不能な ブートROM(マスクROM) です。ここから、権限を少しずつ渡しながらアプリケーションへ到達する多段の受け渡しが始まります。各段は「次に動かすコードを検証してから制御を移す」という同じ役割を持ち、これを積み上げたものが 信頼の連鎖(chain of trust) です。CPU が電源投入直後にどうやって最初の命令番地を得るかの一般論は /hardware-components/ や /os/ のブート系記事も参照してください。
| 段 | 実体 | 格納場所 | 主な仕事 |
|---|---|---|---|
| ブートROM | マスクROM(不変) | オンチップROM | リセット直後に実行。1次ブートローダを検証してロード |
| 1次ブートローダ (BL1/FSBL) | 小さな信頼済みコード | 内蔵フラッシュ/OTP近傍 | クロック・DRAM 初期化、2次ブートローダを検証 |
| 2次ブートローダ (BL2/U-Boot 等) | 機能豊富なローダ | フラッシュ | 起動対象の選択、A/B切替、リカバリ、OTA適用 |
| アプリ/OS | RTOS・Linux・ベアメタル | フラッシュ/eMMC | 本来の機能を実行。ここで初めて業務ロジックが動く |
なぜ多段にするのか。ブートROM は不変ゆえに 信頼の起点(Root of Trust) になれますが、容量も機能も乏しく、DRAM 初期化のような重い処理は担えません。そこで「小さく確実に検証できる段」から「大きく高機能な段」へ、検証の裏付けを保ったまま段階的に能力を拡張します。各段が次段の署名を確かめてからジャンプするので、途中の1段でも改竄されれば連鎖は切れ、起動は止まります。
Secure Boot が成立する前提は、連鎖の最初の1段が書き換え不能であることです。ブートROM 自体を攻撃者が差し替えられるなら、その先の署名検証はすべて意味を失います。だから検証の起点はマスクROM やヒューズ(OTP)に固定され、公開鍵のハッシュ(またはその指紋)も eFuse に焼き込んで不変化します。ソフトで守れるのは「不変な起点があって初めて」です。
ファーム更新の本質:現用面を壊さずに置き換える
OTA(Over-The-Air)更新の核心は無線通信ではなく、いま動いている実行イメージを、途中で電源が落ちても壊さずに新しいものへ差し替える ことにあります。素朴に「現用のフラッシュ領域を上書きする」設計は、消去・書込みの途中で電源が切れると現用イメージが半端に壊れ、二度と起動しない 文鎮化(bricking) を招きます。フラッシュは消去単位(セクタ)が大きく、書込みにも時間がかかるため、この中断は現実的な脅威です。
したがって更新は必ず 別の領域 に書きます。方式は大きく2つに分かれます。
| 方式 | 領域構成 | 更新中の動作 | 長所 | 短所 |
|---|---|---|---|---|
| A/B(デュアルバンク) | 同容量のスロットAとBを常設 | 旧面で稼働したまま新面へ書く | 無停止・即ロールバック可能 | フラッシュ容量が約2倍必要 |
| 単一バンク+リカバリ | アプリ1面+小さな回復用ローダ | リカバリへ落ちてアプリ面を書換え | 容量を節約できる | 更新中は本機能が止まる |
| 差分(デルタ)更新 | 上記に差分適用を追加 | 旧イメージ+差分から新面を再構成 | 転送量が小さい | 適用中の一時領域と検証が必須 |
A/Bバンクと原子的な切替
A/B 方式では、同じ容量のスロット {A,B} を用意し、一方を 現用(active)、他方を 待機(inactive) とします。更新はつねに待機面へ行い、現用面には指一本触れません。書き込みが完了したら、どちらを次に起動するかを示す ブート順(boot order) を切り替えます。ここで決定的に重要なのが、この切替が 原子的(atomic) であることです。
更新シーケンス(A が現用、B へ更新する例)
1. B を消去し、新イメージを B へ書き込む … A は無傷で稼働中
2. B の署名・ハッシュを検証 … 壊れていれば B を捨てて終了(A のまま)
3. ブート順を「次回は B を試す」に更新 ← ここが原子的でなければならない
4. 再起動 → BL2 が B を起動(試験起動 / trial)
5. B 上のアプリが自己診断に成功したら commit … B を active に確定
失敗 or ウォッチドッグ発火 → 次回は A へ自動ロールバック
手順3を「原子的」にするとは、ブート順の記述が 完全に旧状態か完全に新状態のどちらかにしかならず、中間状態が観測されない ことを指します。フラッシュ上の1ビット・1ワードの書換えでフラグを反転させ、その書込みが単一のフラッシュプログラム単位で完了するよう設計します。もし切替情報を複数ワードにまたがって更新し、その途中で電源が落ちると、ブートローダが「A とも B とも解釈できない」宙ぶらりんの状態を読み、どちらへも起動できなくなります。これを避けるため、各スロットに世代番号(generation/sequence)と有効フラグを持たせ、より新しく、かつ有効印が確定している面を選ぶ といった規則で、部分書込みを安全側に倒します。
署名検証は「イメージが正規で壊れていない」ことは保証しますが、「そのファームが実機で正しく動く」ことまでは保証しません。設定の非互換やドライバの不整合で、起動はするのに通信できない、という事故は起こります。そこで新面はまず 試験起動(trial boot) とし、アプリが自己診断に通って初めて commit(恒久化)します。commit 前に再起動が起きたら自動で旧面へ戻る——この「保護観察期間」が、検証をすり抜けた論理的欠陥に対する最後の安全網です。
署名検証:正しさと真正性をどう確かめるか
OTA イメージには2種類の保証が要ります。完全性(壊れていないか) と 真正性(正規の発行元が作ったか) です。完全性だけならハッシュ(SHA-256 など)で足りますが、ハッシュは誰でも再計算できるため、攻撃者が改竄イメージと改竄後のハッシュをセットで送れば通ってしまいます。真正性を担保するには、発行元だけが持つ 秘密鍵で署名 し、機器側は対応する 公開鍵で検証 します。
署名(開発側) 検証(機器側)
digest = SHA-256(image) digest' = SHA-256(受信 image)
sig = Sign(privKey, digest) ok = Verify(pubKey, sig, digest')
pubKey は eFuse に焼いた鍵ハッシュと突合
→ privKey は HSM 内に保管し外に出さない
→ 機器には pubKey(の指紋)だけを不変領域に持たせる
ポイントは、機器側が持つのは 公開鍵(またはそのハッシュ)だけ で、秘密鍵は決して機器へ入れないことです。公開鍵は漏れても署名の偽造には使えないため、フラッシュに置いても安全です。多段ブートでは各段がこの検証を繰り返し、ブートROM が BL1 を、BL1 が BL2 を、BL2 がアプリ(および OTA イメージ)を検証することで、電源投入から更新適用まで一貫した信頼の連鎖が保たれます。
署名が正しくても、攻撃者が「正規に署名された古い脆弱版」を再配布すれば、既知の穴を持つファームへ機器を巻き戻せます。これがロールバック(ダウングレード)攻撃です。対策は各イメージに単調増加する セキュリティ版数(anti-rollback counter) を持たせ、その最小許容値を eFuse など巻き戻せない領域に記録すること。検証時に「イメージの版数が機器の記録値以上か」を確認し、古い版数の署名済みイメージを拒否します。署名検証と版数チェックは別物で、両方が要ります。
電源断に耐えるフェイルセーフの設計原則
ここまでの要素を「いつ電源が落ちても壊れない」という一点に集約すると、設計原則は次の順序制約に凝縮されます。現用面を破壊しうる操作は、新面が完全に用意され検証されるまで一切行わない。 具体的には、書込み→検証→切替→試験→確定の順を厳守し、各段の途中でリセットが入っても直前の安定状態へ戻れることを保証します。
- 書込み中の失敗:待機面が壊れるだけ。現用面は無傷なので、次回起動は従来どおり成功し、OTA を再試行すればよい。
- 切替直後の失敗:ブート順は原子的に更新済みなので中間状態はない。試験起動が始まる前に落ちれば、確定していない切替を無効化して旧面へ戻す規則で救済する。
- 試験起動中の失敗:ウォッチドッグタイマ が commit されないまま満了し、強制リセット。ブートローダは未 commit を検知して旧面へフォールバックする。
ウォッチドッグは、新ファームが起動途中でハングした(例外ループや初期化デッドロック)ケースの最終防波堤です。新面のアプリは起動後の一定時間内に自己診断を終えて commit するか、ウォッチドッグを叩き続ける責務を負い、それが果たせなければハードウェアが問答無用でリセットをかけて旧面へ戻します。
実装で最も危険なアンチパターンは、ブート順を先に新面へ向けてから書込みや検証を行うことです。この順序では、書込み途中や検証失敗の時点で電源が落ちると、ブートローダが「壊れた・未検証の新面」を起動対象として選んでしまい、旧面へ戻る術を失って文鎮化します。正しい不変条件は『ブート順が指す面は、常に検証済みで起動可能である』。切替はシーケンスの最後、検証通過後にのみ行います。
「なぜ多段ブートか」には『不変な起点(Root of Trust)から信頼を段階的に拡張し、各段が次段を検証するため』、「A/Bバンクの利点」には『旧面で稼働したまま新面へ書けて無停止、かつ即ロールバック可能』、「文鎮化を防ぐ鍵」には『現用面を壊さず別面へ書き、原子的な切替と試験起動→commit で、どこで電源が落ちても旧面へ戻せること』と答えます。ハッシュ(完全性)と署名(真正性)の役割分担、ロールバック攻撃を版数で防ぐ点も頻出です。
まとめ
- 組込みの起動は ブートROM→1次→2次ブートローダ→アプリ の多段で、不変なブートROM を起点に各段が次段を検証する信頼の連鎖を成す。
- OTA は無線通信そのものより、現用面を壊さず別面へ置き換える 更新設計が本質。A/Bバンクは無停止・即ロールバック、単一バンク+リカバリは容量節約という得失を持つ。
- 切替は 原子的な1ビット書換え で中間状態をなくし、書込み→署名検証→切替→試験起動→commit の順序を守ることで、どの時点の電源断でも旧面へ安全に戻れる。
- 署名は完全性(ハッシュ)と真正性(公開鍵検証)を分担し、ロールバック攻撃は単調増加の版数 で塞ぐ。ウォッチドッグ が起動ハング時の最終防波堤になる。
組込み・IoT Article
ブートローダとOTA更新を実務で読む
TL;DRは入口です。実際に選ぶ・使う段階では、何を解決するか、何と比較するか、導入後にどこで詰まるかまで見る必要があります。
解決すること
組込み
比較で見る軸
難易度: advanced / カテゴリ: 組込み・IoT / タグ数: 5
導入後に効く点
OTAは新旧2つの領域を使い分けて更新する。A/Bバンクは無停止で書け、単一バンク+リカバリは容量を節約する。切替は1ビットの原子的な書換えで確定させる。
先に潰すリスク
用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。
- 難易度
- advanced
- カテゴリ
- 組込み・IoT
- タグ数
- 5
判断チェックリスト
- 自社の用途が「組込み / ブートローダ」に近いか確認する。
- 強みである「組込みの起動は多段:ROMブート→1次ブートローダ→2次ブートローダ→アプリと権限を渡し、各段が次段を検証してから制御を移す(Secure Boot の信頼の連鎖)。」が本当に評価軸になるか確認する。
- 注意点の「用語だけ覚えても、設計・実装・運用でどこに効くかを確認しないと判断を誤る。」を運用で吸収できるか確認する。
- 公開値や仕様値は、対象プラン・対象機種・対象リージョンまで確認する。
- 既存システム、ID、ネットワーク、監視、バックアップとの接続方法を先に洗い出す。
- 小さく試してから、本番移行、権限設計、障害時手順、コスト監視を決める。