內存保護單元(MPU)是一種硬件機制,通過只允許代碼訪問需要的內存和外設來提高嵌入式設備的安全性。應用程序可以組織為進程(process),每個進程訪問自己的內存和外設。MPU不僅阻止應用程序代碼訪問其指定區域以外的內存或外設,而且還可以用于檢測堆棧溢出。
我們基于ARM Cortex-M MCU中的MPU,討論一下MPU所提供的一些特性。
什么是MPU?
MPU:Memory Protection Unit,內存保護單元。它是一種硬件機制,只允許需要訪問某些資源的代碼訪問相應的內存和外設。 MPU常用于安全關鍵應用,如醫療設備、航空電子設備、工業控制、核電站等,提高嵌入式應用的穩定性和安全性。
在IoT應用中,也可以通過MPU限制對內存和外設的訪問,提高產品的安全性。例如,可以通過MPU機制隱藏加密密鑰以拒絕攻擊者訪問;使用MPU隔離Flash控制器也可以防止攻擊者更改應用程序,只允許受信任的代碼執行代碼更新。
通過MPU將RTOS任務劃分為進程,如圖1所示。每個進程可以包含任意數量的任務。進程內的任務可以訪問分配給該進程的內存和外設。增加MPU時,從任務的角度幾乎不用更改,除非任務之間存在交互。
圖1 應用隔離為進程
進程間可以通過共享內存進行通信,兩個進程的MPU配置表中將出現相同的區域。
應用中也可以包含具有完全權限的系統級任務和ISR,允許它們訪問所有內存、外設或CPU。
當違規發生時,系統的行為取決于應用程序,是哪個任務違規。例如,如果違規是由圖形用戶界面(GUI)造成的,可以終止并重新啟動GUI,并且不會影響系統的其它部分。但是,如果違規任務控制一個制動器,則異常處理程序可能需要在重啟任務之前立即停止制動器。理想情況下,在產品開發過程中會捕獲并糾正訪問違規,否則,系統設計人員將需要評估所有可能的結果,并決定發生這種情況時該做什么。
使用MPU檢測堆棧溢出
在基于RTOS的應用中,每個任務都需要單獨的堆棧空間。堆棧溢出可能是基于RTOS的系統開發人員所面臨的最常見的問題之一。如果沒有硬件幫助,堆棧溢出檢測可以由軟件實現,但軟件方式不能及時捕獲溢出,可能導致產品不穩定。MPU可以幫助防止堆棧溢出。
如圖2所示,MPU域可用于堆棧溢出檢測。使用一個小的域(RedZone)來覆蓋每個任務堆棧的底部。配置MPU屬性,如果有任何代碼嘗試寫入該區域,將觸發MPU異常。域的大小決定了該方法在捕獲堆棧溢出方面的效率。區域越大,堆棧溢出捕獲的可能性就越大,同時堆棧可用的RAM就越少。
換言之,RedZone域被認為是不可用的內存,它被用來檢測非法寫入。開始時可以將RedZone大小設置為32個字節,如果任務堆棧為512個字節,那么32個字節約占用6%,剩余480個字節的可用堆棧空間。
圖2 使用MPU域檢測堆棧溢出
另一種檢測方法是將整個任務堆棧封裝為一個MPU區域,允許讀寫操作。該方法有兩個問題:首先,在Cortex-M(ARMv7M架構)上,堆棧的大小需為2的冪,并且必須在冪邊界(即32、64、128、256、512等)上對齊。如果嵌入式應用有足夠的RAM,那么這不是問題;然而,在資源受限的應用 (如Cortex-M MCU)中,在減少浪費的同時設置合適的堆棧內存會很復雜。其次,該方法不允許寫同一進程中的其它任務堆棧,不允許進程中的任務通過堆棧傳遞信息。
進程表
進程表中的條目數量取決于MPU。Cortex-M架構MCU可以有8個或16個MPU區域。由于可用的區域數量有限,通常更多設置區域保護RAM中的數據訪問。但是,如果應用程序沒有用完所有區域,也可以通過區域限制對代碼的訪問來提高安全性。
圖3展示了一個包含四個任務的進程的區域定義,所有進程將共享相同的Flash代碼空間。
圖3 一個擁有4個任務的進程MPU域
1、一個MPU域提供對代碼空間的讀取和執行訪問。因為常量通常存儲在Flash中(ASCII字符串、查找表、常量等),所以讀訪問是必須的。
2、一個區域用于提供進程相關的外設訪問,例如,以太網控制器、USB控制器等。MPU區域設置為讀寫訪問,但不允許執行代碼。如果進程不需要訪問外設,則不需要此MPU區域。
3、一個MPU區域封裝進程全局變量以及堆(heap)空間。MPU區域將設置為讀寫權限,但同樣不允許執行代碼。
4、一個MPU區域用于檢測堆棧溢出。此方法假設進程中的任務不會通過其堆棧共享數據。同樣,在此區域中不允許執行代碼。RTOS負責選擇運行哪個任務,相應任務的堆棧將被封裝在MPU區域中。
5、該區域顯示了由于MPU區域大小和對齊限制而可能導致的RAM浪費。資源受限的嵌入式應用中應嘗試盡量減少浪費。
6、此MPU區域用于建立多個進程共享的RAM。如果進程不需要共享數據,則不需要此MPU區域。
進程表由“N”個條目組成,每個條目包含兩個字段:區域的基地址和指定區域屬性(區域大小,允許讀、寫或執行等)。
進程表在創建任務時被分配給任務。RTOS只是在任務的控制塊(TCB)中保留一個指向進程表的指針。RTOS在上下文切換時增加更新MPU進程表的代碼,如圖4所示。在切換任務時不需要保存MPU配置。
圖4 RTOS上下文切換時,更新MPU配置
Cortex-M 特權等級
上電后,Cortex-M運行在特權模式,可以訪問CPU的所有資源,訪問任何內存或I/O地址,啟用/禁用中斷,設置嵌套向量中斷控制器(NVIC),配置FPU和MPU等。
為了保證系統的安全,特權模式代碼保留給經過完全測試并受信任的代碼。由于大多數RTOS都經過了完整的測試,通常被認為是值得信任的,而應用代碼是不可信的。也有例外,例如,只要ISR保持盡可能短而不被濫用,ISR通常被認為是受信任的,因此也以特權模式運行。這是大多數RTOS供應商的建議。
應用代碼在非特權模式下運行,從而限制了代碼可以做的事情。具體來說,非特權模式可以防止代碼關中斷、更改嵌套向量中斷控制器(NVIC)的設置、將運行模式更改為特權等級、修改MPU設置等。這是一個理想的特性,因為我們不希望不受信任的代碼賦予自己特權,從而更改系統設計者提供的保護。
由于CPU總是以特權模式開始運行,任務需要從創建時就以非特權模式運行,或者在啟動后通過API調用,切換到非特權模式。一旦進入非特權模式,CPU只有在中斷或異常服務中才能切換回特權模式。
在用戶模式訪問RTOS服務
由于非特權代碼不能禁用中斷,因此應用代碼被迫使用RTOS服務來訪問共享資源。由于RTOS服務在特權模式下運行,因此非特權任務必須通過Cortex-M提供的SVC機制切換回特權模式。SVC的行為類似中斷,但由CPU指令觸發。
在Cortex-M上,SVC指令使用一個8位參數來指定256個可能的RTOS服務。設計者決定非特權代碼可以使用的RTOS服務。例如,你可能不希望允許非特權任務終止其它任務(或其本身)。此外,這些服務都不允許禁用中斷,因為這將破壞在非特權模式下運行代碼的目的。一旦調用,SVC指令跳轉到SVC異常處理程序。
SVC處理過程如圖5所示。
圖5 用戶代碼調用RTOS服務
在Cortex-M3上,SVC處理程序將增加約1k字節的代碼,需要執行75~125條CPU指令。因此,相同RTOS服務,在非特權代碼中調用比特權模式調用需要更多的處理時間。
在非特權模式下運行代碼還可以防止用戶代碼禁用中斷,從而減少了鎖定系統的機會。當然,如果用戶代碼進入無限循環,特別是在高優先級任務或ISR中時,鎖定仍然可能發生。在這種情況下,通過使用看門狗可以恢復。
進程間通信
圖6展示了進程之間的通信方式。這些只是一些可能的場景,實際上,應用程序可以使用這些技術的組合。
圖6 進程通信方式
1、互斥量用于確保兩個進程不同時訪問相同的數據。注意,互斥量駐留在RTOS內存空間中,通過RTOS API,所有進程可以訪問該互斥量。
2、需要訪問受保護資源的任務必須首先獲取互斥量。當任務完成共享資源訪問后,互斥量將被釋放。沙漏表示可選超時,當任務不希望永久等待所有者釋放互斥量時,可以使用超時機制。
3、信號量也可以用來指示數據可用。
4、進程中的任務將數據存儲到共享內存中,然后發出信號。
5、進程B中的任務等待進程A的信號。沙漏表示一個可選的超時,以避免永遠等待信號。如果該信號未在規定的時間內發生,則RTOS將恢復該任務。在這種情況下,任務知道共享區域中沒有存入任何東西。
6、如果沒有發生超時,進程B確認數據處理完成。
7、進程A發信號量后,等待具有可選超時的確認。
8、通信也可以使用RTOS的消息隊列機制。此時,從共享RAM區域動態分配緩沖區,進程A中的發送任務填充緩沖區,并將指針發送給進程B中的任務。
9、與信號量情況類似,等待進程B中的任務確認,并指定一個可選的超時。
內存和I/O訪問錯誤處理
進程間可以通過共享內存進行通信,兩個進程的MPU配置表中將出現相同的區域。
應用中也可以包含具有完全權限的系統級任務和ISR,允許它們訪問所有內存、外設或CPU。
當違規發生時,系統的行為取決于應用程序,是哪個任務違規。例如,如果違規是由圖形用戶界面(GUI)造成的,可以終止并重新啟動GUI,并且不會影響系統的其它部分。但是,如果違規任務控制一個制動器,則異常處理程序可能需要在重啟任務之前立即停止制動器。理想情況下,在產品開發過程中會捕獲并糾正訪問違規,否則,系統設計人員將需要評估所有可能的結果,并決定發生這種情況時該做什么。
MPU的工作是確保進程中的任務只能訪問分配給它的內存和外設。但是,如果任務試圖訪問允許區域以外的數據呢?MPU會觸發一個稱為內存管理(MemManage)故障的CPU異常。
當故障發生時,系統行為取決于應用程序,但如何處理故障可能是很難確定的事情。首先,這些類型的故障應該在開發過程中被檢測和糾正。然而,使用MPU的原因之一是為了防止發生的無效內存或外設訪問,要么是因為系統驗證期間未捕獲某些偶發情況,或者是未經授權的訪問。
MemManage故障通常由RTOS處理。理想情況下,嵌入式系統有一些機制可以記錄和報告故障,以便在產品的下一個版本中修正。文件系統是記錄這些故障的好地方,當然,還取決于故障處理程序。
發生故障時,故障處理程序可以執行以下操作序列(偽代碼):
void OS_MPU_FaultHandler (void) { // Terminate the offending task/process (1) // Release resources owned by the task/process (2) // Run a user provided ‘callback’ (based on the offending task) (3) // If we have a file system: (4) // Store information about the cause // Do we restart the task/process? (5) // Yes, Restart the task/process // Alert a user (6) // No, Reset the system (7) }
(1)當故障發生時,設計者需要確定如何操作。至少必須終止違規的任務,但我們是否還需要終止此進程中的其他任務?沒有一個確定的答案,事實上,這可能取決于是哪個任務造成了故障。因此,MPU故障處理程序應根據觸發它的任務或進程執行不同的操作。
(2)被終止的違規任務(或進程)可能擁有內核對象、緩沖區、I/O等資源。這些資源需要被釋放,以避免影響其他任務/進程。
(3)導致故障的任務可能會控制制動器或其他類型的輸出,需將任務置于安全狀態,以避免對人員或資產造成傷害。嵌入式系統設計者應提供用戶定義的回調函數,以處理系統特定的操作。在任務創建過程中,將回調函數存儲在任務的控制塊(TCB)中。為提高系統安全性,只能在系統啟動時創建任務,此時CPU處于特權模式;運行時只能在故障時刪除任務。由于TCB位于RTOS空間中,因此無法從用戶代碼訪問回調函數,從而防止潛在的不安全和不可靠的代碼無意中或惡意地調用回調函數。
(4)如果嵌入式系統具有數據存儲功能,則可以記錄故障相關的信息,如違規任務的性質、CPU寄存器的值、所采取的操作等。
(5)根據導致故障的任務,可以重新啟動,使系統可以錯誤中恢復。
(6)如果系統能夠恢復,并且如果系統包含顯示,則警告提示非常有用。此外,如果系統具有網絡連接,則通知服務部門和開發團隊可以在將來的版本中避免此問題。
(7)如果系統無法恢復,除了重置系統之外,可能沒有其他選擇。
可以更改MPU進程表,使其包含每個任務的回調函數,以便檢測到故障時從RTOS上下文切換代碼調用。如果所有的任務都需要對故障執行相同的操作,那么可以不使用此功能,或者讓所有MPU進程表都指向相同的回調函數。后一種選擇更最靈活,因為它為未來的版本提供了更大的靈活性。但你可能需要咨詢RTOS供應商,以確定此功能是否可用。
建議
以非特權模式運行用戶代碼
使用MPU時,以特權模式運行應用代碼。這意味著應用程序代碼將能夠更改MPU設置,并破壞使用MPU的目的。以特權模式運行應用可能更容易地遷移應用代碼。在某些時候,大多數應用程序代碼將需要在非特權模式下運行,用戶需要添加SVC處理程序。
ISR具有完全訪問權限
當識別到中斷并且啟動ISR時,處理器將切換到特權模式。由于MPU控制寄存器的PRIVDEFENA為1,因此ISR可以訪問所有I/O內存。
此外,ISR應該盡可能短,并簡單地發信號給任務,由任務執行中斷設備所需的大部分工作。當然,這假設ISR是內核感知的ISR,并且該中斷設備有相當多的工作處理。例如,不應在ISR中上處理以太網數據包。然而,閃爍LED或更新脈沖寬度調制(PWM)計時器的占空比可以直接在ISR中完成。
防止在RAM中執行代碼
大多數MPU可以防止從RAM執行代碼,從而限制代碼注入攻擊。防止外設執行代碼可能看起來很奇怪,但可以防止想方設法進入系統的黑客。
限制進程對外設的訪問
應該留出一個或多個MPU區域,以限制進程只能訪問自己的外設。換言之,如果一個進程管理USB端口,那么它應該只能訪問USB外設或與USB控制器的需求相關的外設,例如DMA。
限制可用的RTOS API
設計者必須確定應用代碼可使用哪些RTOS API。例如,是否禁止應用代碼在系統初始化后創建和刪除任務或RTOS對象?換言之,RTOS對象是否只能系統啟動時創建,而非運行時創建?如果是,SVC處理程序查找表應只包含向應用公開的API。即使ISR在特權模式下運行,可以訪問所有的RTOS API,一個好的RTOS也會阻止從ISR中創建和刪除RTOS對象。
在RTOS空間中分配RTOS對象
任務堆棧位于進程空間內,但是,RTOS對象(信號量、隊列、任務控制塊等)應在內核空間中分配,并通過引用進行訪問。不應在進程空間中分配RTOS對象,因為這意味著應用代碼可以不調用RTOS API,即可有意或意外地修改內核對象。
保護對代碼的訪問
雖然MPU區域通常用于提供或限制對RAM和外設的訪問,但如果有空閑區域,并且能夠通過進程組織代碼,那么限制對代碼的訪問非常有用,可以防止某些類型的安全攻擊,如Return-to-libc攻擊。
減少進程間的通信
正如設計時,盡可能保持任務獨立一樣,進程也應該遵循同樣的規則。因此,進程間應盡量不交互,或者將進程間通信保持在最低限度。如果必須與其它進程通信,只需留出一個包含Out和In緩沖區的共享區域。發送者將其數據放入Out緩沖區中,然后觸發一個中斷來喚醒接收進程。一旦數據被處理,響應可以放在發送者的In緩沖區中,并且可以使用中斷來通知發送進程。
確定出現MPU故障時應如何操作
理想情況下,在開發過程中檢測并糾正所有MPU故障。你還需考慮由于意外故障或錯誤或系統受到安全攻擊而出現的故障處理。在大多數情況下,建議對每個任務或每個進程設置一個可控的關機順序。是否重新啟動違規任務、進程內或整個系統內的所有任務都取決于故障的嚴重程度。
記錄和報告故障
理想情況下,系統應該能夠記錄(可能是文件系統)并顯示故障原因,以便開發人員解決問題。
大多數RTOS在特權模式下運行應用代碼,使得應用程序完全控制CPU及其資源。所有任務和ISR都可以不受限制地訪問內存和外設。這意味著應用程序代碼可能會意外或故意損壞其他任務的堆棧或變量。通過MPU機制,可以限制應用代碼的訪問權限,極大提供應用的功能安全。
審核編輯:劉清
-
mcu
+關注
關注
146文章
17149瀏覽量
351216 -
ARM
+關注
關注
134文章
9097瀏覽量
367586 -
MPU
+關注
關注
0文章
359瀏覽量
48803 -
Flash控制器
+關注
關注
0文章
6瀏覽量
8059 -
Cortex-M
+關注
關注
2文章
229瀏覽量
29763
原文標題:MPU在哪些方面保護內存安全?
文章出處:【微信號:strongerHuang,微信公眾號:strongerHuang】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論