一顆晶片正在驗證新韌體。這次簽章失敗,CPU 應該繼續等待。成功、失敗、reset 與 timeout 的測試都符合規格,團隊卻還不能據此判定放行路徑安全。
原因在於 CPU 不會重新計算簽章。它讀取的是保存下來的結果。如果故障把結果從 0 改成 1,CPU 還會被擋住嗎? 先沿著這個值往後追,比先挑一種保護編碼更有用。
本系列 RTL Anti-Tampering Design/RTL 防竄改設計小教室,從暫存器、控制訊號與 FSM 開始,再走向 netlist 分析與實體驗證。第一課要分清兩個目標:正常流程算對答案,以及故障介入後仍不交出未授權操作。兩者需要不同的測試證據。
本課約 25~35 分鐘,包含閱讀與練習。先備知識是同步 register、簡單 FSM、valid/ready 介面;簽章演算法本身可先看 Secure Boot 小教室。下文晶片是虛構的教學設計;RTL 片段與 Python 模型用來找反例,尚未做 RTL 編譯、綜合、形式證明或實體 fault injection 測試。
1. 簽章沒過,CPU 卻開始跑了
想像圖書館有一間需要先審核的資料室。你提出的申請不合格,館員仍會完成審核,留下『已審核』與『准入』兩個欄位。前者對應 checked_q,後者對應 verified_q。讀者在門口提出請求,門口也能接待,還須准入才會交付資料。這三項對應 valid、ready 與 grant;交付紀錄對應 accepted_commit。
館員判斷正確,准入欄卻被人從 0 改成 1,仍可能錯交資料。這個故事只讓我們追結果的保存與使用;館員不會在每次交付時重新審核,硬體也沒有因此多出一位獨立館員。
先沿著一次開機走一遍。驗證器(checker)檢查韌體映像,判斷它有沒有執行資格;暫存器把這個結果保存下來;最後由使用結果的邏輯(consumer)決定是否放行。這個 consumer 可能是 reset controller、bus firewall,或 CPU 的取指介面。
「取指」就是 CPU 讀取程式指令(instruction fetch)。本課先用一個簡化介面描述它:CPU 想取得一條指令,介面也準備好接受這筆請求,但仍須有執行許可才能放行。因此我們會追問的,不只是「簽章算得對不對」,還有「這筆未授權的取指,最後有沒有被接受」。
互動實驗 · 教學模型
簽章失敗以後,哪一步仍會放行?
先跑無故障的失敗映像,再改成保存 bit 翻轉。逐步觀察 checker 的答案、暫存器與接受緣;完整正文與原圖仍在下方。
單 bit 設計翻 verified_q;四位元設計翻選定的位置。這是各設計分別承受一次故障的比較。
先預測:保存結果被改成 1,下一個取指會不會被接受?
reset 後 checked_q=0。先前結果無效,這一刻不接受取指。
grant=0 · accepted=0grant=0 · accepted=0grant=0 · accepted=0失敗映像且沒有故障,三條路徑都不放行。接著試試保存結果翻轉,觀察同一個 consumer 會怎麼讀 Q。
ref_auth_complete=0 · ref_auth_pass=0monitor 與最後的閘在本次模型中可信。它是驗證用的對照,不是已實作的晶片防護。
碼字實驗|1001 要改幾個 bit 才變成 0110?
點選 bit,或從 16 個碼字選一個值。完整比較只把 0110 解成 True。1001 是合法 False,其餘 14 個碼字不屬於這兩個教學表示。
目前超過一個 bit 已改變,超出上方單 bit 故障模型。此處是碼字探索,沒有宣稱這種故障能在矽上實現。
比較成立只回答碼字是不是 0110。回到上方「來源判斷翻轉」,可觀察合法 True 也可能來自錯誤判斷。
時序實驗|alert 出現前,操作已經發生了嗎?
這是獨立的時序示意,另行假定有偵測器。t0 的故障已使 grant 錯誤升高;第一個可接受取指的邊緣是 t1,t5 才 reset。調整阻擋與通報的時間,觀察首次接受。
- t0故障接受歷史:無
- t1首次接受接受歷史:有
- t2alert接受歷史:有
- t3等待接受歷史:有
- t4等待接受歷史:有
- t5reset接受歷史:有
已有一次未授權接受。後續 alert、局部阻擋或 reset 都不會刪掉這筆歷史。
把 alert 調到 t1,但保留「不做局部阻擋」,仍會接受。通知與 grant 是不同路徑;同一拍看見 alert,不會自動改寫 grant。這些延遲是教學設定,沒有模擬偵測器、reset controller 或真實時序裕量。
本區只計算 0/1 值。一次故障改一個目標;保存翻轉維持到 reset。checker 原始答案、monitor、clock、reset 與閘可信。四位元路徑固定 phase_ok=1、local_fault=0;未模擬 X、glitch、綜合或實體注入。
下面是刻意脆弱的同步設計。auth_ok_i 表示 checker 的判斷;verified_q 在判斷完成後保存它。所有值都在同一時脈域,reset 開始一個新的開機嘗試。
// Deliberately vulnerable teaching fragment.
always_ff @(posedge clk_i or negedge rst_ni) begin
if (!rst_ni) begin
checked_q <= 1'b0;
verified_q <= 1'b0;
end else if (verify_done_i && !checked_q) begin
checked_q <= 1'b1;
verified_q <= auth_ok_i;
end
end
assign exec_grant_o = checked_q && verified_q;
assign accepted_commit = fetch_valid_i && fetch_ready_i
&& exec_grant_o;
沿著圖讀程式會更清楚:上方的 AND 決定這次要不要寫入,兩個暫存器共用同一個 enable。完成檢查後,checked_q 已經變成 1,因此正常寫入路徑會停下來。下方的放行邏輯卻仍持續讀取 Q;故障如果改到保存結果的位元,即使沒有下一次寫入,也可能改變 grant。
正常情況下,失敗的簽章讓 checked_q=1、verified_q=0,放行閘關閉。若在驗證完成後,故障把 verified_q 翻成 1,閘就打開;這時 valid、ready 都成立,取指便被接受。密碼核心甚至可能從頭到尾都算對。
這裡兩個旗標的工作不同:checked_q 記錄「已經檢查過」,verified_q 保存「檢查是否通過」。失敗映像也可以讓第一個旗標變成 1,因為檢查確實做完了。當第二個旗標被故障改寫,後端看到的兩個 1 就不再忠實代表剛才的驗證結果。
停止正常寫入,並沒有替保存值增加完整性保護。Enable 決定 RTL 何時更新暫存器;本課的故障則直接改動保存狀態。若只測「驗證結束後不再寫入」,這條反例不會出現在一般功能測試裡。
這段程式不是 OpenTitan 的 RTL,也沒有指出某顆現售晶片的漏洞。它要讓你看到:驗證器的正確答案,可以在送到使用者的途中被改掉。 review 不能只停在 auth_ok_i 的產生處。
先跑最小的一條路
沿著這張入館申請看 E0~E2:E0 清空紀錄,E1 後留下『已審核、拒絕』。兩次鐘響之間,有人改准入欄;E2 門口照改過的欄位交付資料。原始審核簿仍記拒絕,所以那次交付越權。這裡只借鐘響表示先後,E1 是本課局部示例的完成緣;第二課完成在 edge 2,不能把兩張時程表當成同一張。
第一次操作上方實驗台,先看沒有故障的失敗映像,再選保存結果翻轉。先不改四位元編碼與 alert 延遲。每次只改一項,才能把接受原因追回那個改動。
| 時刻 | 保存值與判準 | 接受事件 |
|---|---|---|
| E0 reset | checked=0、result=0 | 沒有放行 |
| E1 取樣前 | 尚未保存完成結果 | 不使用 E1 更新後的值 |
| E1 更新後 | checked=1、result=0;reference 仍判定失敗 | 尚未接受請求 |
| E1 到 E2 之間 | 將一位元 result 改成 1,checked 保持 1 | 故障改的是保存答案 |
| E2 取樣 | valid=ready=1,grant=1;reference pass=0 | accepted=1,形成越權反例 |
這張表固定了完成、注入與請求的先後。E2 的接受使用當時已穩定的值。改動 E2 更新後的狀態,不能回頭改寫 E2 已記錄的接受。互動台是兩態教學模型;表中 E0~E2 是這條最小示例的局部編號,不是第二課的 edge 排程。
2. 先說清楚:允許什麼故障?
圖書館先訂一條小實驗規則:一次申請中,只准動一次保存的准入欄,其他人與鐘都先照規則工作。兩次來拿資料仍共用這一次動手機會,不能到門口就重新領預算。若截止前沒來拿,紀錄裡沒有交付,只能說這份行程沒看到越權;不能說有人發現並攔下改字。這些『先信任』是實驗範圍,沒有保證門口或鐘永遠改不了。
當設計文件寫著「能防 fault injection」,驗證團隊仍需要知道該怎麼測。攻擊者能碰哪個暫存器?一次能改幾個位置?改過的值只維持一拍,還是會一直留下來?這些條件會改變結果,也就是本課所說的故障模型(fault model)。
第一個反例先縮小範圍,只測驗證結果已經寫入之後,一個 bit 被翻轉的情況:
| 項目 | 本課設定 |
|---|---|
| 保護目標 | 未授權的映像不能得到一次被接受的取指 |
| 故障位置 | checker 已完成後的 verified_q |
| 故障效果 | 一次 0→1 翻轉,值保留到下一次覆寫或 reset |
| 故障預算 | 每次開機嘗試至多一次、單一位置 |
| 排除位置 | checker、monitor、clock、reset、grant 閘與下游在此實驗中可信 |
| 觀察期限 | 從 reset 結束到第一次取指,或測試宣告的截止週期 |
這次先隔離一個問題:結果暫存器改錯,是否足以放行?因此表內明確排除其他目標。它們不是天生安全,而是這次實驗先不改動。之後加入比較器、next-state 邏輯或 consumer,必須重新找反例,不能沿用先前結論。
保留觀察期限同樣重要。若請求在截止後才到來,測試可能看不到保存錯誤造成的越權。報告應寫「在這個窗口內未觀察到接受」,而不是直接宣稱故障已被偵測或阻擋。
電壓、clock、EM 與雷射是實體擾動方法;bit flip、stuck-at、某段組合邏輯輸出被改寫,是分析使用的邏輯模型。兩者之間要靠量測與時序分析建立關係。Python 把一個值換掉,不代表雷射一定能在矽上產生同樣效果。
還要分清楚安全性與可用性。這一課的安全目標容許故障導致停機;合法映像能否在無故障時順利開機,仍須另外驗證。把 grant 永久接成 0 可以守住拒絕性質,卻做不出可用的產品。
3. Register 和 FSM 的共同問題
同一張申請也能揭開流程問題。牌子依序寫『等待、審核、可交付』,就像 WAIT、CHECK、RELEASE。有人把審核時讀到的成功條件改成真,館員沿原本允許的路換成『可交付』牌;牌名和換牌路線都合法,申請卻沒有通過。四格准入欄與兩份紀錄各能檢查指定改動,無法單靠外形或副本數補出真實授權。
把 verified_q 改成 4 bits,或替 FSM 加 default: ERROR,都是值得分析的設計動作;它們各自只保護部分路徑。
| 設計動作 | 能處理的部分 | 仍要找的反例 |
|---|---|---|
| 結果採多位元編碼,完整比對 True | 某些 bit 翻轉落到非法碼時拒絕放行 | 上游單一 auth_ok 先被翻轉,再被編成合法 True |
| FSM 採稀疏編碼,非法態進 ERROR | 模型內把 state 打成非法碼的故障 | cmp_pass 被改寫,FSM 沿合法弧進入 RELEASE |
| 放兩份 register,持續比較 | 可偵測部分不同步的儲存錯誤 | 共用輸入、clock 或比較器一起出錯;綜合合併副本 |
| 偵測後送 alert | 讓系統知道有異常並啟動回應 | consumer 在 alert 生效之前已接受敏感操作 |
FSM 的反例尤其容易漏看。假設狀態是 WAIT → CHECK → RELEASE;CHECK 中以 cmp_pass 決定要去 RELEASE 還是 ERROR。故障把失敗判斷改成成功,控制器就沿原本存在的 CHECK→RELEASE 弧走下去。狀態碼合法、這條弧也存在,但授權條件是假的。
圖中的問題出在分岔路口:cmp_pass 選了哪一條路?如果只檢查 RELEASE 是不是合法狀態,就看不到這次放行是由錯誤條件觸發的。讀者可以先用手指沿紅色路徑走一遍,再回頭問「原本哪個判斷應該擋住它」。
讀 FSM 時,可以先分開檢查三件事。第一件是目前的 state 編碼是否屬於合法狀態;第二件是這次轉移所需的條件是否成立;第三件則是走到這裡之前,需要完成的工作是否真的做過。前兩項看起來正常,仍不能省略第三項。
例如 digest 已經算完,代表我們取得了摘要,卻不能用來代替「簽章驗證已完成,而且通過」。同樣地,done 告訴你某項工作做完了,good 才是另一個需要確認的結果。控制器如果把這些不同意義的訊號混在一起,就可能在錯的時候交出權限。
這裡先建立問題,後面的課再算 Hamming distance、選 register 編碼並分析 FSM。多位元表示法不是把一個 bool 加寬後就完成安全設計。
4. 抓住 accepted commit,別只盯 alert
資料已在 t+1 交到讀者手上,t+2 才有人通報,t+5 才關門。關門可以停止後續交付,無法讓 t+1 的交付沒發生。因此要在交付簿上看 accepted_commit,再對照 alert 與 reset。若只有『准入』亮起、讀者尚未提出請求,還沒有交付;故事也不能把每次亮燈都算成一次資料外流。
判斷越權,要看下游是否接受操作。旗標升高只是內部訊號,不能單靠它判定資產是否已交付。本課把簡化介面的接受事件稱為 accepted commit,讓波形有一個明確的判讀位置。
在簡化的取指介面中,fetch_valid 表示有一筆有效請求,fetch_ready 表示介面準備好接受,exec_grant 則是執行許可。三者在約定的接受時脈邊緣同時成立,才記錄一次 fetch_valid && fetch_ready && exec_grant。這裡的 commit 指操作被介面接受,不是用來描述完整 CPU 的指令執行流程。
換成金鑰讀取介面,觀察點可能是敏感資料已被回應端交付的握手。因此每個設計都要自己找出「資產或權限真正交出去」的事件。真實系統若還有 prefetch、DMA 或其他 bus master,要把那些路徑一起列入,不能只守 CPU reset。
若故障在 t 發生,t+1 已接受取指,t+2 才發 alert,t+5 才 reset,單純檢查「最終有 alert」仍然會通過。但執行權已經交出去;之後 reset 無法撤回那次取指。
設計時要同時處理局部阻擋與系統回應。局部機制在指定故障模型下守住 grant,alert handler 再負責通報、升級或清理。這兩件事的期限不同,應分別寫性質與測試。
回到開場的失敗映像,驗證問題是「是否有任何一次未授權取指被接受」。即使同一條 trace 後來出現 alert,答案仍是有。把接受與告警分開記錄,才能看出防護是否真的來得及。
5. 把授權契約寫成 RTL 與 SVA
門口改用四格通行卡,只認完整的 0110,還要看到已審核、目前可交付且沒有局部錯誤。這些依序對應 result_code_q、checked_q、phase_ok_i 和 local_fault_i。稽核員另拿原始審核簿,查每次交付是否真有資格;這才對應獨立 reference。稽核規則寫在紙上不會自己變成一道門,SVA 也只是驗證要求,不是新增的阻擋電路。
第一步先把 consumer 的輸入條件寫清楚。以下 AUTH_TRUE=0110、AUTH_FALSE=1001 是教學用的兩個碼字,Hamming distance 為 4;不是完整的 OpenTitan primitive。result_code_q 只在本次開機嘗試的判斷完成後有效。phase_ok_i 表示 consumer 目前允許交出執行權,local_fault_i 是局部錯誤訊號。
localparam logic [3:0] AUTH_TRUE = 4'b0110;
localparam logic [3:0] AUTH_FALSE = 4'b1001;
assign exec_grant_o = checked_q
&& (result_code_q == AUTH_TRUE)
&& phase_ok_i
&& !local_fault_i;
assign accepted_commit = fetch_valid_i && fetch_ready_i
&& exec_grant_o;
圖裡有兩道要分開看的問題:比較器先回答「收到的碼字是不是 0110」,放行閘再確認完成、階段與局部錯誤條件。比較器沒有追查這個碼字是怎麼產生的,所以合法碼字的來源仍需要自己的保護與驗證。
完整比對有明確的用途:非法碼字不能因為其中一個 bit 為 1,就被當成通過。它的限制也很明確:若來源先出錯,再編成合法 True,格式檢查仍會接受。保存完整性與來源授權必須分開分析。
假定輸入只有 0/1,FALSE 的單一 bit 翻轉不會變成 TRUE,完整比較會拒絕它。但 checker、比較器、grant 閘與取指介面仍有自己的故障面。本段展示的是消費端條件,不宣稱做完了防竄改設計。
== 在四態模擬可能得到 X。驗證環境應檢查 grant 與控制輸入沒有未知值,並將 X 視為測試失敗;不要假設四態模擬的 X 就等於矽上某一種 fault。若作業另外接低有效 reset,名稱應清楚表達 cpu_rst_n=1 是解除 reset;實務上還要處理解除 reset 的同步與 glitch,不適合把上述組合式 grant 直接當完整 reset controller。
接下來用 SVA 把要求寫下來。第一條先檢查接線契約:當操作被接受時,放行邏輯要求的條件是否都有成立?這能幫忙抓到漏接或接錯條件的問題,但這些條件本身也可能被故障改寫。
第二條才拿獨立的可信觀察結果來比對:被接受的操作,是否真的來自本次已完成且通過驗證的映像?第三條 cover 則確認合法操作有機會走到接受點,避免設計因為永遠不放行,讓拒絕性質看起來一直通過。
// Interface contract: useful, but mostly checks the gate's wiring.
assert property (@(posedge clk_i) disable iff (!rst_ni)
accepted_commit |->
checked_q && (result_code_q == AUTH_TRUE)
&& phase_ok_i && !local_fault_i);
// Security goal: ref_* belongs to a trusted verification monitor.
assert property (@(posedge clk_i) disable iff (!rst_ni)
accepted_commit |-> ref_auth_complete && ref_auth_pass);
// Avoid a vacuous result: authorized work must be reachable.
cover property (@(posedge clk_i) disable iff (!rst_ni)
ref_auth_complete && ref_auth_pass ##[1:8] accepted_commit);
回到第一個 bit 翻轉反例,DUT 可能送出 accepted_commit=1,可信參考卻仍記得映像驗證失敗。這個不一致才會讓安全性 assert 失敗。若把 verified_q 直接複製成參考值,兩邊一起變成成功,反例就被藏起來了。
|-> 的後件檢查同一個採樣時脈。此處介面約定是在 rising edge 接受取指,控制訊號在該 edge 前已穩定;若你的介面在另一拍提交,monitor 與性質必須跟著改。
ref_auth_complete、ref_auth_pass 要由可信 testbench/formal monitor,依本次映像與獨立規格建立,並跨本次開機嘗試保存。不能拿故障目標 verified_q 再命名成 ref_auth_pass。 否則錯誤旗標與「證據」一起變成 1,assertion 仍會通過。monitor 是本次實驗的可信邊界;真實硬體若需要獨立驗證器,還得分析它的實作、共用路徑與成本。
disable iff 排除了 reset 被宣告的區間,因此這組性質也沒有驗證 reset glitch 或 reset 本身受攻擊的情況。cover 只確認某條成功路徑可達,不等於完整 liveness 證明。
手算碼距,再讀兩種 assertion
把拒絕卡 1001 的第二個低位翻掉,得到 1011,門口完整比對 0110 就會拒絕。可是若審核來源先被改成成功,製卡員會印出完整 0110。查『門口照卡開門嗎』可能通過,查『原申請真的核準嗎』才會失敗。兩張稽核表對應接線 assertion 與 reference assertion;卡片圖樣的四格差距不等於四次獨立審核,也不是實體防偽難度。
Hamming distance(漢明距離)是兩個等長位元串中,不同位置的數量。先算 1001 XOR 0110 = 1111,再數四個 1,得到碼距 4。翻轉保存值的 bit 1,只會得到 1001 XOR 0010 = 1011。完整比對 0110 會拒絕它。這個例子假設編碼前的判斷、比較器與接受端都可信。
SVA 是 SystemVerilog Assertions,用來寫取樣時刻的性質。DUT 是被測設計,reference 是測試平台獨立保存的判準。它們不是同一份結果的兩個名稱。
現在另開「編碼前來源被改」的故障目標。reset 已解除,E2 前所有輸入都穩定:checked=1、result_code=0110、phase_ok=1、local_fault=0、valid=ready=1。DUT 算出 grant=1,因此 accepted=1。Reference 則記錄 complete=1、pass=0。要求「accepted 時 DUT 的 grant 條件成立」會通過;要求「accepted 時 reference 通過」會失敗。前者核對接線,後者核對授權。來源被改後,編碼器仍能產生合法的 0110;這不是單一保存位元翻轉穿過碼距 4 的例子。
Cover 的 ##[1:8] 是允許後續 1~8 個取樣緣出現接受,並尋找至少一條符合的路徑。它不保證每次合法工作都完成,也不是產品的八拍期限。Assertion 與 cover 的 reset 排除條件、注入預算及 reference 可信假設,都要由實際 harness 配合;本課尚未執行形式證明。
6. 做一次可重跑的故障練習
練習時,先重跑不合格申請卻改准入欄的交付,再試四格卡的單格改動,最後試製卡前改判斷。每次重新開始一份申請,保留原始拒絕判準,對照保存欄、門口看到的值與交付。Python 或實驗臺重現這個行程,只支持指定模型的結果;沒有真的找人改卡,也沒有執行 RTL 或晶片測試。
下載 lesson01_fault_model.py,使用 Python 3 執行:
python lesson01_fault_model.py
模型只處理 0/1 值與指定離散時序,使用標準函式庫。預期輸出是六個反例/對照案例,加上 PASS: 16 codewords + 4 single-bit faults + 6 scenarios。這裡的 PASS 表示模型確實重現預期結果,包含故意脆弱的設計被繞過;它不表示晶片通過安全驗證。
| 案例 | 你應該觀察到什麼 |
|---|---|
| 正常合法映像 | 脆弱設計與條件式阻擋設計都能放行 |
| 正常非法映像 | 兩者都拒絕 |
| 結果 bit 被翻轉 | 脆弱設計放行;假定可信 reference 的對照閘拒絕 |
| 上游判斷被翻轉後再編碼 | 結果是合法 True;碼字比較本身抓不到 |
| 合法 state 的錯誤轉移 | 檢查 legal state 會通過;歷史/授權檢查失敗 |
| alert 晚於 commit | 已接受非法操作,後續 alert 不能修復這個性質 |
練習分三步。第一步先不改程式,寫下每個案例的 trusted boundary。第二步檢查所有 16 個碼字,確認完整比較只接受 0110,並算出 1001 的四種單 bit 翻轉。第三步改變假設:如果 fault 也能打到 reference checker,或最後的 grant 閘,對照模型還能保證什麼?把新反例寫出來,不要只把預期結果改成 PASS。
若你有 RTL simulator,可將教學片段包進 testbench,在驗證失敗後注入結果 register 的翻轉,記錄 accepted_commit、alert 與 reset 的時間。force/release、backdoor write 與專用 injection mux 的保持時間可能不同;注入器的行為也要寫進 fault model。本課沒有執行這項 RTL 實驗。
7. 用三種資料回答三種問題
檢查圖書館時,流程圖可回答資料從哪裡交付,規章可回答誰有權進,某次實地演練則只回答當次怎麼被改。讀技術來源也照這樣分工:架構圖、RTL 說明與故障分析各有條件。別因為別間館用了相似門禁,就把它的驗證結果貼到自己的交付簿上;本課原有來源也沒有替這個虛構閘背書。
SYNFI 回答 netlist 上的故障問題。 它以 SAT 分析合成後電路的故障效果,並以 OpenTitan 為案例。閱讀時先找子電路邊界、輸入/狀態設定、fault 數量與有效故障定義;結果只在這些設定下成立。它沒有替整顆晶片或所有實體注入方式提供安全保證。SYNFI 原文
SCFI 回答 FSM 控制流程的問題。 它強化 next-state 計算,納入控制資訊與執行歷史。這正好對應第 3 節「合法 state 仍可能走錯流程」的反例;本課沒有重現 SCFI 電路或論文的實驗結果。SCFI 原文
Hot Chips Titan 回答系統如何交出執行權。 2018 年投影片第 19 張展示驗證主機韌體後解除系統 reset;第 33~34 張談 Titan 自身的分階段 verified boot。拿它來理解架構很合適,但不能因此宣稱本課 RTL 閘已具有 Titan 的物理安全能力。Titan 官方投影片
再看一個可檢視的實作:OpenTitan rom_ctrl 分開傳遞 done 與多位元 good,並將 ROM 摘要送往 key manager。文件也提醒,good 是額外檢查,ROM 信任還涉及 key derivation 與 lifecycle 政策。因此本文簡化的「簽章過才取指」規則,不能當成所有 OpenTitan lifecycle 的完整開機規格。rom_ctrl 文件
把這幾份資料放一起,是為了建立 review 問題:結果怎麼保存、consumer 怎麼使用、流程歷史怎麼確認,以及實作後用什麼模型再查一次。每份資料負責的層次不同。
8. 回到你的設計,先畫一張授權表
替資料室填授權表時,從『哪次交出資料』往回找門口、保存欄與審核來源。若側門也能領資料,正門不放行仍不夠;這對應 debug、DMA 等其他 consumer。故事幫我們列路徑,沒有證明每扇門都已檢查,更不能把所有欄位一律印成四格卡就當成同一種防禦。
開始審查自己的設計時,可以先挑一個會交出權限的動作,例如 CPU 取指、debug unlock、OTP programming 或 secret read。先找到它被接受的那個事件,再往回追:是誰放行、讀了哪個結果、結果存在哪裡,又是由誰產生?這樣就有一條可以逐段檢查的路徑,不必一開始就列出全晶片的每個 flop。
| 你要填的欄位 | 本課例子 |
|---|---|
| 敏感動作與接受事件 | CPU 取指,accepted_commit |
| 可信授權依據 | 本次映像通過獨立簽章規格與版本政策 |
| 產生、保存、消費 | checker → result register → fetch gate |
| 各段 fault targets | comparator output、register、next-state、decode、grant |
| 偵測與阻擋期限 | 下一次可能接受取指的 edge 前完成局部阻擋 |
| 共用失效來源 | clock、reset、電源、輸入、decode、綜合後共用邏輯 |
| 驗證證據 | fault model、反例波形、assertion、cover、版本與工具限制 |
對每一段追問:「這裡變成成功,前面真的完成了什麼?」如果答案只是一個同源的 bit,就把它列成下一輪要攻擊的目標。也別漏了替代路徑:CPU reset 還沒解除時,debug 或 DMA 是否已經能讀敏感資料?
這份表不是把每個位置都加上相同編碼。結果暫存器需要分析保存完整性;FSM 要分析轉移條件與歷史;接受端則要確認阻擋期限。先知道哪一段會怎樣失效,才能選擇有針對性的措施。
**本課完成條件:**你能解釋單 bit 放行的反例,寫出一份有邊界的 fault model,並把安全性質放在被接受的操作上。下方測驗會再檢查這三件事。
9. 工程延伸:下一輪要改變哪些假設?
下一輪若准許動門口判斷或審核完成欄,就要另開一份實驗規則,不能沿用『只改准入欄』的通過紀錄。也可能從被拒絕的回應猜到資料內容,那是另一個洩漏問題。這對應新增 fault target 與機密性觀察;沒有未授權交付,只回答交付契約,不能連帶保證不洩漏秘密。
給 RTL/DV 工程師:從這一課走到專案驗證
先建立無故障 baseline,再注入 fault。每次 campaign 記錄目標、效果、持續時間、數量、初始狀態與觀察終點。detected、blocked before commit 與 unauthorized commit 應分開統計;timeout 和未覆蓋的 case 保留為未定。
第二輪把故障移到 checked_q、比較器輸出、FSM 條件與 grant 邏輯。第三輪分析重複注入及共用來源。若設計改成冗餘 flop 或稀疏 FSM,檢查實際綜合後的 netlist 是否保留原本的結構;RTL 上有兩份不代表 gate level 還有兩份。OpenTitan 的設計指南將這些綜合問題列為需要關注的項目。硬體設計指南
這些步驟還沒有處理所有機密性問題。Faulty ciphertext 可能洩漏 key;被阻擋或沒有改變輸出的 fault,也可能透過統計選擇性洩漏資訊。後續 DFA/SIFA 課程會把「沒有非法 commit」與「沒有資訊洩漏」分開討論。
本課拒絕性質只回答未授權操作有沒有被接受。若產品還要保護秘密資料,應再建立洩漏目標與觀察模型。不要把同一個 PASS 標籤用來涵蓋兩種不同問題。
10. 系列路線:從 register 走到驗證證據
這間資料室的檢查會逐步往前後展開:先查保存卡,再查流程牌、來源與交付期限,最後查實際製作後的路徑。這就是後續課程從 register 走向 FSM 與驗證證據的順序。目前第一至第十課已發布,後六課仍規劃中;故事裡能想像一套完整館規,不代表尚未發布的課程或晶片驗證已完成。
本系列規劃 16 課,目前已發布第一至第十課,另有六課規劃中。完整大綱、每課目標與發布狀態統一放在 RTL Anti-Tampering Design 課程頁。
先從 register、FSM 與權限交接理解失效,再走到故障實驗、實體模型校準與證據包。每一課都會用 Wrap-up 整理威脅模型、成因、解法、驗證與限制。
11. 參考資料與閱讀作業
這篇是原創教學綜整,資料查核日期為 2026-10-03。Claude 與 Grok 提供獨立唯讀研究;本文的取捨、來源查證、程式與網站驗收由 Codex 完成。程式案例、表格與圖解是本課建立的教學模型,並非論文實驗的重製結果。
- Pascal Nasahl 等,SYNFI: Pre-Silicon Fault Analysis of an Open-Source Secure Element,TCHES 2022(4),56–87。作者版/DOI。作業:找出分析子電路與有效 fault 的定義,列出不在模型內的兩個位置。
- Pascal Nasahl 等,SCFI: State Machine Control-Flow Hardening Against Fault Attacks,DATE 2023;preprint 2022。作者版/DOI。作業:用第 3 節的 FSM 解釋 state integrity 與 control-flow integrity 的差別。
- Victor Arribas、Felix Wegener、Amir Moradi、Svetla Nikova,Cryptographic Fault Diagnosis using VerFI,IEEE HOST 2020,229–240。作者機構摘要/DOI。這裡核對書目與摘要,未聲稱重現完整論文;留作第 14 課的 gate-level 診斷閱讀。
- Scott Johnson 等,Titan: enabling a transparent silicon root of trust for Cloud,Hot Chips 30,2018。官方投影片。作業:區分主機 reset release 與 Titan 自身 verified boot,對照投影片第 19、33~34 張。
- lowRISC,OpenTitan ROM Controller: Theory of Operation。官方文件。作業:追
done、good、digest 的不同 consumer。線上 master 文件會更新,專案驗證請固定版本。 - lowRISC,Secure Hardware Design Guidelines。官方文件。作業:找出冗餘被綜合最佳化的風險,檢查你自己的交付流程是否包含 netlist 複驗。
MY ACADEMY · LESSON FILM
教學影片
影片依序說明本課的資料路徑。看完一段,可以回到下面的互動練習,改變輸入或故障條件。動畫呈現教學模型;它沒有替代 RTL 模擬。
旁白使用合成聲音。互動教學與動畫均有模型邊界;請以本課的來源與驗證範圍解讀結果。
Wrap-up|把這一課帶回設計審查
- 威脅模型與成立條件
回到開場的晶片:新韌體沒有通過簽章驗證,CPU 原本應該繼續等待。本課要守住的動作是「取指」,也就是 CPU 讀取程式指令。在這個簡化模型裡,只要未授權映像得到一次被接受的取指,就已違反我們設定的保護目標。
第一個反例只允許攻擊者改動一個地方:驗證完成後,把保存結果的 verified_q 從 0 翻成 1。每次開機嘗試至多發生一次,改過的值會保留到下一次覆寫或 reset。測試從 reset 結束開始觀察,直到第一次取指,或事先指定的截止週期。
這一輪先假設驗證器 checker、觀察結果的 monitor、clock、reset、最後的 grant 放行閘與下游介面都可信。這是刻意縮小的實驗範圍,方便先看清楚一個暫存器出錯會造成什麼。後面的案例若改打其他位置,就必須重新說明條件,不能沿用這一輪的保證。
- 失效原因
驗證器可能從頭到尾都算對了,但 CPU 的放行邏輯讀到的是暫存器裡的結果。若那個結果在保存或傳遞途中被改掉,後端便可能把失敗當成成功。因此,審查要一路追到使用結果的地方,不能只停在驗證器的輸出。
FSM 也有類似的問題。控制器可以沿著原本存在的路徑,進入一個編碼合法的 RELEASE 狀態;真正出錯的卻是「這次是否有資格走這條路」。同樣地,把結果改成多 bit,仍要檢查編碼之前的單 bit 判斷,以及編碼之後的解碼與放行閘。
- 防護方法
先確認下游真正接受操作的時刻。本課把這個事件稱為 accepted commit:在約定的時脈邊緣,取指請求有效、介面準備好,而且執行許可也成立。防護需要在這個事件發生之前擋住未授權操作;事後才送出 alert,無法改變那次操作已被接受的結果。
接著逐段確認放行依據:本次驗證是否真的完成、結果是否符合完整碼字、流程是否處在允許階段,以及是否已有本地錯誤。必要時還要檢查先前的授權歷史。這些條件如何取得可信證據、是否共用同一個容易出錯的來源,都要另外分析;把更多條件接成 AND,並不會自動得到完整的安全保證。
- 驗證方式與待做檢查
先重跑本課的六個反例與對照案例。不要只看最後印出的 PASS;那代表教學程式重現了預期結果,其中也包含脆弱設計被成功繞過的情況。請沿著時間順序看:故障何時發生、取指何時被接受、阻擋與 alert 又在何時生效。
這次已完成的是有限 Python 模型的示範。下一輪若允許故障打到最後的 grant 閘、checker 或共用來源,就要重新找反例,而不是把原本的通過紀錄直接搬過去。每份結果都應附上它相信哪些元件、排除了哪些目標,以及觀察到哪個時間點。
- 防護界線與未驗證項目
這個模型把指定位置的值改掉,用來理解授權路徑上的失效;它沒有證明實體擾動一定能在矽上造成同樣效果。本課也尚未完成 RTL simulation、綜合、形式證明或 silicon 驗證。因此,這裡的示範不能用來宣稱某個 IP 已通過防竄改驗證。
安全性之外還要顧到可用性。把 grant 永久關閉,確實不會放行未授權映像,但合法韌體也永遠不能啟動。我們仍需要另外確認:無故障時合法映像能正常使用,發生故障時則依明確政策阻擋或復原。本課先以取指被接受作為保護邊界,沒有推論完整 CPU 或整顆 SoC 都已安全。
換個情境再想一次
現在只改一個假設:攻擊者除了結果暫存器,也能打到最後的 grant 放行閘。先畫出原本的阻擋路徑,再想一想,若最後一關的輸出被改寫,前面的檢查還能不能阻止那次取指?請列出仍然可信的元件,並寫下一個可能繞過防護的反例,或一項需要重新驗證的性質。
以上整理對照本課的教學案例、參考資料與實驗範圍;未列為已完成的驗證,都是後續工作。
學習指南
RTL Anti-Tampering Design|RTL 防竄改設計
查看課程大綱 → · 進度只計入已發布課程
先備知識
- 同步 register、基本 FSM 與 valid/ready 介面
我學會了什麼
- 追蹤授權路徑到被接受的操作
- 寫出故障模型與可信邊界
- 區分 state 合法性、歷史與授權
- 區分局部阻擋與延遲 alert