加密、身份驗證和其它安全性方法能夠有效地保護通過網際網路傳輸的資料和程式更新。也就是說,除非有一端很容易被駭客竊取密鑰、並可能植入供將來啟動的惡意軟體,否則安全仍有所保障。然而,系統營運商不知道的是:機密資訊每天都在被盜,並可能造成嚴重的服務中斷。

自從2005年推出Cortex-M架構以來,出貨了大量基於Cortex-M MCU的產品,其中許多產品都連接到網際網路。目前許多新產品都使用Cortex-M MCU進行開發,而且由於物聯網(IoT)的財政誘因,越來越多的Cortex-M MCU都將連接到網際網路。然而,在大多數情況下,這些嵌入式裝置很少、或根本沒有防禦駭客的保護措施。

目前已經上市以及還在開發中的大多數Cortex-M MCU都具有記憶體保護單元(Memory Protection Units;MPU)。然而,由於交付產品的時間緊迫以及使用Cortex-M MPU時的困難,這些MPU不是未能物盡其用,就是根本沒使用。由於MPU的面積必須是2的次方數,才能使其尺寸邊界一致,因而明顯佔用了大量的記憶體,並成為記憶體有限的系統導入時的另一障礙。

然而,對於這些MCU,MPU和管理程式呼叫(SVC)指令是實現可接受安全性的唯一手段。因此,我在一年半前著手確定是否能夠克服MPU的問題、是否可能設計出在開發專案的後期和後續進行升級的實用方法,以及使用MPU安全的新計劃。我發現這樣做是切實可行的,並且已經在開發MPU-Plus來簡化這一過程。

現有的所有嵌入式系統都使用了Cortex-v7M架構。一年前發佈的Cortex-v8M架構可提供更好的安全保護。遺憾的是,處理器供應商並沒立即採用Cortex-v8M,幾乎所有新的MCU仍然使用Cortex-v7M架構。因此,Cortex-v7M架構還將沿用一段時間。因此,本文提供了一個將現有系統移植到Cortex-v7M MPU的步驟。

移植

我最近完成了將大量中介韌體移植到使用Cortex-M MPU的非特權模式(umode)分區。所移植的程式碼包括:

  • 檔案展示
  • 檔案系統
  • USB大量儲存級驅動器
  • USB主機堆疊
  • Synopsys主機控制驅動器

總而言之,這相當於大約20,000行程式碼——可不是什麼輕鬆的任務。此外,我從該移植過程學習到的經驗是:這真的可行!而且不一定要撰寫程式碼的作者本人來完成。

我對上述程式碼略知一二,但並不是作者。此外,它是在幾年前編寫的,而且很不適合移植到MPU。此次移植的重要性在於證實了:為使用Cortex-M MPU處理器的系統開發後期和後續,提高系統安全的可行性。

關於專案系統的後期:雖然做一些前期安全規畫絕對是個好主意,但安全性的確會為開發專案增添額外複雜性。實際上,這些專案很難按時完成功能設計目標。增加安全性可能會導致設計工作量的浪費,並延遲專案交付。我意識到這種異端思想應被放逐到網路的黑暗角落,但它卻又很務實。我認為最好在製造過程中就增加安全性——亦即等到值得保護的東西出現時。

關於專案系統的後續,我確定有很多經理和工程師非常關心其系統在現場的脆弱程度——特別是如果這些系統連接到駭客公路(即網際網路)。在此也有了希望,只要這些產品包含Cortex-M MPU而且軟體可以升級。

安全事實

在討論逐步移植過程以提高安全性之前,我們需要認清一些現實:

  • 沒有完美的安全性
  • 極目標在於比駭客能得手的安全程度更安全
  • 升安全性是一個漸進過程——可能需要很多次反覆運算和許多年,才能達到理想的程度

改善安全性的方法主要是將所有易受攻擊和不可信的程式碼放入umode,並將金鑰、安全軟體(如加密、認證、啟動和程式碼更新)以及關鍵任務程式碼放入特權模式(pmode)。MPU用於防止ucode超出其指定區域並限制如何存取這些區域(例如,XN=永不執行)。被駭客竊取的ucode不能破壞pcode、竊取pdata、從資料區域執行、溢出堆疊,也無法執行其它駭客的把戲——因為它已經被鎖入櫃中。

分區

分割(partition)程式碼是第一步。圖1顯示可稱為「直通」(first pass)的分區——即不好高騖遠。在此例中,只有5個分區:以非特權模式(umode)運行的應用程式和中介韌體;以特權模式(pmode)運行的初始化、系統服務和任務關鍵程式碼。 每個分區都包含一或多項任務。

20180514_MPU_TA31P1 圖1:分區(來源:Micro Digital)

在正常任務操作開始之前運行初始化程式碼,它通常在MPU關閉或開啟且同時啟用背景域(BR)的情況下運行。因此,此程式碼可以存取與執行任何內容。因為它是安全啟動的一部份,理論上不能被駭客攻擊。

系統服務包括例外處理常式、RTOS和帶有金鑰的安全軟體。關鍵任務軟體通常是執行主要工作的少量軟體。這些都是可信賴的軟體。(當然,關鍵任務程式碼和金鑰是我們試圖保護的弱者)。pmode程式碼是由umode程式碼保護——經由受到特許處理器狀態支援的MPU區域,以及對於以SVC指令實現的系統服務提供軟體中斷(SWI)應用程式介面(API)。圖1中的粗線表示umode和pmode之間的隔離保護。

如果有效地實現硬體強制分區,那麼滲透至任一umode分區的惡意軟體都無法存取pmode分區。

記憶體保護單元

記憶體保護單元(MPU)共有8個插槽,每個插槽可以包含一個區域(region)。圖2顯示pmode和umode的典型MPU結構。

20180514_MPU_TA31P2 圖2:記憶體保護單元結構(來源:Micro Digital)

區域7 (Region 7)是保留給任務堆疊的區域,由調度程式管理。該區域允許立即檢測任務堆疊溢位並防止堆疊中的程式碼執行。pmode中的兩個sys區域允許關閉BR以隔離ptasks。這是從ptasks到utasks的逐步轉換過程所必需的,如下所述。

其它區域從每個任務的記憶體保護陣列(MPA)載入。task_code和task_data區域是特定於任務或其分區的。在pmode中,pcom_code和pcom_data區域在兩或更多任務或區域之間是共通的;而在umode中,ucom_code和ucom_data在兩個或更多任務或區域之間共通。這將在以下的逐步過程討論中更充份的解釋。

命令和狀態暫存器(Command and Status Register;CSR)區域是I/O區域。umode中的syn_csr和ur1_csr (Synopsys USB和UART1)由於其MPU插槽不足而包含在pmode中的apb0 csr內。由於umode MPU具有更多插槽,因此可在umode中提供較佳安全性。

記憶體保護陣列

如圖3所示,每個任務都有自己的記憶體保護陣列(MPA)。MPA與任務控制表中的TCB順序相同。每個MPA都是其相關任務運行時MPU動態部份的副本。調度程式在分配其任務時將MPA載入MPU中。

另外,如圖3所示,MPA範本確定了MPA的內容。範本可以在MPA中共用。對於同一分區內的任務,情況往往如此;雖然它們不需要具有相同的範本,而且可能只是共用某些區域。

20180514_MPU_TA31P3 圖3:每個任務的MPA(來源:Micro Digital)

每個MPA都是由名為rbar和rasr的兩個32位元欄位組成的結構陣列。這些是每個MPU插槽中MPU RBAR和RASR暫存器的精確副本,只是有效位元設置在rbar中,而非RBAR內。以下是一個範本的例子:

const MPA mpa_tmplt_usbh = { {RA("ucom_data") | V | 0, RW_DATA | N57 | RSIC(udsz) | EN}, {RA("ucom_code") | V | 1, UCODE | N7 | RSIC(ucsz) | EN}, {RA("usbh_data") | V | 2, RW_DATA | RSIC(usbdsz) | EN}, {RA("usbh_code") | V | 3, UCODE | N67 | RSIC(usbcsz) | EN}, {RA("syn_csr") | V | 4, RW_DATA | RSIC(synsz) | EN}, {RA("ur1_csr") | V | 5, RW_DATA | RSIC(ur1sz) | EN}, };

這對應於圖2所示的umode MPU結構。Pmode也有類似範本。

創建區域

範本由幾個區域組成。那麼如何創建域呢?方法之一是首先在應用的C來源程式碼模組中定義區段(section)。例如,在包含特定區域功能變數程式碼的每個C模組中,使用以下命令啟動程式碼:

#pragma default_function_attributes = @ “.ucom_code” // Place all ucom functions here. #pragma default_function_attributes =

“ucom_code” 是標識包含utasks之間通用程式碼區段的區段名稱。對於特定於taskA的程式碼,請使用例如“.taskA_code” 這樣的區段名稱。選用區段名稱前面的“.”是為了與標準編譯器區段名稱(例如.text、.bss等)保持一致。可以在pragma之間包含任意數量的函數。此外,上述結構可以在其它模組中重複使用,而且所有的函數都將由連結器(linker)組合到單個.ucom_code區段中。

針對資料使用:

#pragma default_variable_attributes = @ “.ucom_data” // Place all ucom data here. #pragma default_variable_attributes =

與程式碼一樣,任何數量的變數都可以包含在pragma之間,上述結構可以在其它模組中重複使用以創建包含所有ucom變數的單個.ucom_data區段。當然,這也可用於包含單個任務資料的區段,如.taskA_data。

在ILINK連結器命令檔案(.icf)中,對於.ucom_code區段使用以下命令:

define exported symbol ucsz = 0x8000; … define block ucom_code with size = 0x7000, alignment = ucsz {ro section .ucom_code}; … place in ROM {block ucom_code, …};

如果需要的話,可在ucom_code區塊中放置其它區段,也可以按固定順序放置。在本例中,只有.ucom_code區段被放置在ucom_code區塊中。(請注意,區段和區塊名稱由“.”區分。)

在上面定義範本的C檔案中,放置:

#pragma section="ucom_code" extern u32 ucsz;

現在,可以在MPA範本定義中使用ucom_code和ucsz (如上一節所示)。因此,連結區塊成為MPA區域。這很方便,因為它可以輕易地控制其大小、排列、順序和其它特徵。

連結器區塊

如上所述,連結器命令檔案(.icf)中的區塊成為MPA範本中的區域,並最終成為MPU區域。使用連結程式塊可以減輕分配錯誤的大小和/或校準錯誤。如果一個區塊對於它所包含的區段(們)來說太小,連結器將會「抱怨」。相反地,連結器映射檔將顯示連結器區塊是否過大。

要將.icf內的大小和校準分配給連結器區塊,我們必須區分三種大小:區域大小、標稱大小和實際大小。例如,假設ucom_code的實際大小是0x6B16。那麼,區域大小必須是下一個更大的冪= 0x8000。將區域大小除以8得到子區域大小= 0x1000。現在,找到N並使其成為0x8000 - N * 0x1000> = 0x6B16成立的最大值。在此,N = 1,因此,標稱大小= 0x7000。因此,ucsz = 0x8000,而標稱區塊大小= 0x7000。

最後一個步驟是將N7(子區域7關閉)置於MPA範本中ucom_code區域的rasr欄位中,如上面的mpa_tmplt_usbh所示。如果我們忘記這點,usbh任務將能夠存取連結器在ucom_code區域之後0x1000位元組中放入的任何內容。另一方面,如果我們錯誤地放置了N67(關閉子區域6和7),那麼,當程式碼嘗試在子區域6中進行存取,將會發生記憶體管理故障(MMF)。

最後我們就可以開始轉換了!