HARDWARE SECURITY

RTL Anti-Tampering Design|RTL 防竄改設計第 1 / 16 課

RTL Anti-Tampering Design 第一課:功能正確,為什麼仍不安全?

從 Secure Boot 的一個放行旗標,學會定義故障模型、追蹤 register 與 FSM 的授權路徑,並用 accepted commit 性質檢查防護是否來得及。附 RTL、SVA、雙語圖解與可執行練習。

19 分鐘

一顆晶片正在驗證新韌體。這次簽章失敗,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 的答案、暫存器與接受緣;完整正文與原圖仍在下方。

先預測:保存結果被改成 1,下一個取指會不會被接受?

01 · Checkerauth_ok_i = 0失敗
02 · 結果暫存器verified_q = 0checked_q = 0
03 · 放行閘grant = 0checked_q && verified_q
04 · 取指接受點accepted = 0尚未在 E2 取樣
E0 · reset

reset 後 checked_q=0。先前結果無效,這一刻不接受取指。

單 bit 旗標尚未在 E2 取樣grant=0 · accepted=0
四位元完整比對尚未在 E2 取樣grant=0 · accepted=0
可信參考對照尚未在 E2 取樣grant=0 · accepted=0

失敗映像且沒有故障,三條路徑都不放行。接著試試保存結果翻轉,觀察同一個 consumer 會怎麼讀 Q。

獨立 monitor 保存真實結果ref_auth_complete=0 · ref_auth_pass=0

monitor 與最後的閘在本次模型中可信。它是驗證用的對照,不是已實作的晶片防護。

碼字實驗|1001 要改幾個 bit 才變成 0110?

點選 bit,或從 16 個碼字選一個值。完整比較只把 0110 解成 True。1001 是合法 False,其餘 14 個碼字不屬於這兩個教學表示。

合法 False · 拒絕相對 1001 的翻轉數:0 · 距離 0110:4

比較成立只回答碼字是不是 0110。回到上方「來源判斷翻轉」,可觀察合法 True 也可能來自錯誤判斷。

時序實驗|alert 出現前,操作已經發生了嗎?

這是獨立的時序示意,另行假定有偵測器。t0 的故障已使 grant 錯誤升高;第一個可接受取指的邊緣是 t1,t5 才 reset。調整阻擋與通報的時間,觀察首次接受。

  1. t0故障接受歷史:無
  2. t1首次接受接受歷史:有
  3. t2alert接受歷史:有
  4. t3等待接受歷史:有
  5. t4等待接受歷史:有
  6. 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、綜合或實體注入。

韌體驗證器經過結果暫存器與放行閘,最後讓 CPU 接受取指;故障可能出現在整條路徑。
圖 1:追到真正交出執行權的位置。點圖可開啟完整尺寸。

下面是刻意脆弱的同步設計。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;
兩個具 enable 的暫存器保存檢查完成與通過結果;verified_q 的儲存位元被翻轉後,可能經兩層 AND 放行取指。
圖 2:RTL 的邏輯示意。EN 決定是否更新,CLK 三角形表示上升緣,Rn 圓點表示低有效非同步 reset;這不是綜合後的 netlist。點圖可放大。

沿著圖讀程式會更清楚:上方的 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 resetchecked=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=0accepted=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 弧走下去。狀態碼合法、這條弧也存在,但授權條件是假的。

概念 FSM 的 CHECK 依 cmp_pass 選 ERROR 或 RELEASE;條件被故障改成成功時,合法狀態與合法弧仍可能造成越權。
圖 3:FSM 概念圖。圖中標示改寫轉移條件的反例;未指定 state 編碼,也未畫出完整控制器的所有轉移。

圖中的問題出在分岔路口: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 加一取指已被接受,t 加二才發 alert,t 加五才 reset;局部阻擋必須在接受操作前生效。
圖 4:這是教學時序,不是任何產品的實測延遲。alert 出現時,操作可能已經發生。

若故障在 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完整比較,並與checked_q、phase_ok_i及local_fault_i反相值共同產生grant,再與取指valid及ready形成接受事件。
圖 5:對照第二段 RTL 的組合邏輯。上方只畫碼字輸入,因為片段沒有提供 result_code_q 的寫入電路。

圖裡有兩道要分開看的問題:比較器先回答「收到的碼字是不是 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);
可信驗證 monitor 提供獨立的授權完成與成功參考,assert 在同一取樣緣與可受故障影響的 DUT accepted_commit 比對。
圖 6:安全性 assert 的驗證環境。monitor 位於 testbench 或 formal 環境,並不是這段 RTL 額外實作的晶片內安全電路。

回到第一個 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 targetscomparator 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 完成。程式案例、表格與圖解是本課建立的教學模型,並非論文實驗的重製結果。

  1. Pascal Nasahl 等,SYNFI: Pre-Silicon Fault Analysis of an Open-Source Secure Element,TCHES 2022(4),56–87。作者版/DOI。作業:找出分析子電路與有效 fault 的定義,列出不在模型內的兩個位置。
  2. Pascal Nasahl 等,SCFI: State Machine Control-Flow Hardening Against Fault Attacks,DATE 2023;preprint 2022。作者版/DOI。作業:用第 3 節的 FSM 解釋 state integrity 與 control-flow integrity 的差別。
  3. Victor Arribas、Felix Wegener、Amir Moradi、Svetla Nikova,Cryptographic Fault Diagnosis using VerFI,IEEE HOST 2020,229–240。作者機構摘要/DOI。這裡核對書目與摘要,未聲稱重現完整論文;留作第 14 課的 gate-level 診斷閱讀。
  4. Scott Johnson 等,Titan: enabling a transparent silicon root of trust for Cloud,Hot Chips 30,2018。官方投影片。作業:區分主機 reset release 與 Titan 自身 verified boot,對照投影片第 19、33~34 張。
  5. lowRISC,OpenTitan ROM Controller: Theory of Operation。官方文件。作業:追 done、good、digest 的不同 consumer。線上 master 文件會更新,專案驗證請固定版本。
  6. lowRISC,Secure Hardware Design Guidelines。官方文件。作業:找出冗餘被綜合最佳化的風險,檢查你自己的交付流程是否包含 netlist 複驗。

MY ACADEMY · LESSON FILM

教學影片

影片依序說明本課的資料路徑。看完一段,可以回到下面的互動練習,改變輸入或故障條件。動畫呈現教學模型;它沒有替代 RTL 模擬。

下載 MP4 · 字幕 VTT

旁白使用合成聲音。互動教學與動畫均有模型邊界;請以本課的來源與驗證範圍解讀結果。

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 防竄改設計

0 / 16

查看課程大綱 → · 進度只計入已發布課程

先備知識

  • 同步 register、基本 FSM 與 valid/ready 介面

我學會了什麼

  • 追蹤授權路徑到被接受的操作
  • 寫出故障模型與可信邊界
  • 區分 state 合法性、歷史與授權
  • 區分局部阻擋與延遲 alert

本課術語

查看術語字典 →

延伸閱讀

課後小測驗

1. 無故障的功能測試能建立什麼?
2. cmp_pass 翻轉後,FSM 沿合法弧到 RELEASE。什麼出錯?
3. t+1 commit,t+2 alert。操作被阻止了嗎?
4. ref_auth_pass 應從哪裡來?
5. 上游錯誤判斷被編成合法 True,完整 decode 能抓到來源錯誤嗎?

讀到這裡,辛苦了。

把概念帶走,比把術語背走更重要。

#RTL#Fault Injection#FSM#Secure Boot#Hardware Security#SYNFI#SCFI