前言
眾所周知,Normal World的用戶態與內核態的地址空間隔離是基于MMU分頁來實現的,那么Normal World與Secure World的地址空間隔離是如何實現的呢?
這篇文章將從CPU和OS的角度進行深入分析,并分析其中存在的安全風險。
硬件隔離機制
閱讀ARM TrustZone手冊可知,內存的隔離是由TZASC(TrustZone Address Space Controller)來控制 ,TZASC可以把外部DDR分成多個區域,每個區域可以單獨配置為安全區域或非安全區域 ,Normal World的代碼只能訪問非安全區域。
下面以TZC-380這款地址空間控制器來進行說明,其它型號控制器的原理也大同小異。
通過配置 TZASC的寄存器來設置不同屬性的region,
?一個region表示 一段連續的物理地址空間,
?TZASC給每個region提供了一個可編程的安全屬性域,
?只有在Secure狀態下才允許修改這些寄存器,
?TZASC的基址不是固定的,不同廠商實現可能不同,但是每個寄存器的offset是固定的,如下所示:
CODE-TEEOS內存管理
下面結合【OP-TEE代碼】[1]對配置 TZASC進行分析:
core/drivers/tzc380.c
通過對region對應的控制寄存器進行設置來配置安全內存地址空間:tzc_configure_region
/* *`tzc_configure_region`isusedtoprogramregionsintotheTrustZone *controller. */ voidtzc_configure_region(uint8_tregion,vaddr_tregion_base,uint32_tattr) { assert(tzc.base); assert(regionRegister //注意:regionn的基址寄存器的[14:0]永遠為0,因為TZASC不允許regionsize小于32KB tzc_write_region_base_low(tzc.base,region, addr_low(region_base)); //設置RegionSetupHighRegister,第n個region基址的[63:32]位 //和上面的lowaddr拼成完整的region基址 tzc_write_region_base_high(tzc.base,region, addr_high(region_base)); //設置RegionAttributes Register //控制permissions,regionsize,subregiondisable,andregionenable tzc_write_region_attributes(tzc.base,region,attr); }else{ //第0個region的基址不需要設置,只需要設置region的屬性 tzc_write_region_attributes(tzc.base,region, attr&TZC_ATTR_SP_MASK); } }
//設置RegionSetupLowRegister的值 staticvoidtzc_write_region_base_low(vaddr_tbase,uint32_tregion, uint32_tval) { //定位到第region個Region對應的寄存器,即上圖中的region_setup_low_n //tzasc基址寄存器+regioncontrol寄存器的偏移(0x100)+regionn寄存器的size io_write32(base+REGION_SETUP_LOW_OFF(region),val); }
通過閱讀代碼可知,tzc_configure_region是對第n個region的基址、大小和屬性進行設置 ,其中屬性寄存器的格式如下:
?sp
: 第n個region的權限設置 ,當發生訪問region時,sp控制TZASC是否允許訪問region。
?size
:第n個region的大小 。
?subregion_disable
: region被劃分為8個相同大小的sub-regions,第一位表示相應的subregion是否disabled。
?en
: 第n個region是否開啟。
在imx_configure_tzasc函數中對region進行了配置 :
staticTEE_Resultimx_configure_tzasc(void) { vaddr_taddr[2]={0}; intend=1; inti=0; //TZASC基址 addr[0]=core_mmu_get_va(TZASC_BASE,MEM_AREA_IO_SEC); ...... for(i=0;i
Region 0用來設置整個地址空間的默認屬性,它的基址為0,Size是由AXI_ADDRESS_MSB來配置,因此Region 0除了安全屬性字段之外,其它字段不允許設置。
下面以第一個region為例,對安全屬性進行分析: 第一個region的屬性為TZC_ATTR_SP_NS_RW:
#defineTZC_SP_NS_WBIT(0) #defineTZC_SP_NS_RBIT(1) #defineTZC_SP_S_WBIT(2) #defineTZC_SP_S_RBIT(3) #defineTZC_ATTR_SP_SHIFT28//屬性位[28:31] #defineTZC_ATTR_SP_NS_RW((TZC_SP_NS_W|TZC_SP_NS_R)< ????????????????TZC_ATTR_SP_SHIFT)
根據手冊可知,TZC_SP_NS_W(b0001)是Non-secure write和 Secure write,TZC_SP_NS_R(b0010)是Non-secure read和Secure read,所以TZC_ATTR_SP_NS_RW 表示 Non-secure和Secure狀態可讀寫,即配置了DRAM地址空間的屬性為非安全和安全狀態都可以讀寫。
總結:
以上代碼配置了CFG_TZDRAM_START開始的CFG_TZDRAM_SIZE大小的地址空間為安全內存,即只有安全狀態下(TCR.NS=0)可以訪問。
CFG_DRAM_BASE開始的CFG_DRAM_SIZE大小的地址空間為普通內存,安全和非安全狀態下都可以訪問。
CFG_SHMEM_START開始的CFG_SHMEM_SIZE大小的地址空間為共享內存,安全和非安全狀態都可以 訪問 。
【OP-TEE物理內存布局:】[2]
core/arch/arm/include/mm/generic_ram_layout.h
*TEERAMlayoutwithoutCFG_WITH_PAGER *_ *+----------------------------------+<--?CFG_TZDRAM_START ?*??|?TEE?core?secure?RAM?(TEE_RAM)????| ?*??+----------------------------------+ ?*??|?Trusted?Application?RAM?(TA_RAM)?| ?*??+----------------------------------+ ?*??|?SDP?test?memory?(optional)???????| ?*??+----------------------------------+?<--?CFG_TZDRAM_START?+?CFG_TZDRAM_SIZE ?* ?*??+----------------------------------+?<--?CFG_SHMEM_START ?*??|?Non-secure?static?SHM????????????| ?*??+----------------------------------+?<--?CFG_SHMEM_START?+?CFG_SHMEM_SIZE
至此,已經完成了安全內存的配置,接下來我們再來看下安全OS是如何使用這些物理內存的。
CODE-TEEOS內存管理
core/arch/arm/kernel/entry_a64.S
【TEE OS啟動時會調用core_init_mmu_map對安全內存地址空間進行映射 :】[3]
#ifdefCFG_CORE_ASLR movx0,x20 blget_aslr_seed#x0用來保存開啟aslr的seed #else movx0,#0 #endif adrx1,boot_mmu_config blcore_init_mmu_map#記錄PA和VA的對應關系,并初始化頁表 ...... bl__get_core_pos blenable_mmu#設置ttbr0_el1、tcr_el1,開啟分頁,在開啟頁之前,VA==PA
core_init_mmu_map函數根據編譯時注冊的物理內存地址信息對頁表進行初始化,也就是對物理內存進行內存映射:
void__weakcore_init_mmu_map(unsignedlongseed,structcore_mmu_config*cfg) { #ifndefCFG_VIRTUALIZATION //__nozi_start在鏈接腳本中指定,一級頁表的地址 vaddr_tstart=ROUNDDOWN((vaddr_t)__nozi_start,SMALL_PAGE_SIZE); #else vaddr_tstart=ROUNDDOWN((vaddr_t)__vcore_nex_rw_start, SMALL_PAGE_SIZE); #endif vaddr_tlen=ROUNDUP((vaddr_t)__nozi_end,SMALL_PAGE_SIZE)-start; structtee_mmap_region*tmp_mmap=get_tmp_mmap(); unsignedlongoffs=0; //檢查安全和非安全區域是否有重疊,如果有重疊,則系統panic check_sec_nsec_mem_config(); //static_memory_map記錄mmap_region的PA、VA,用來PA/VA轉換 //第一個mmap_region記錄的是一級頁表的PA和VA static_memory_map[0]=(structtee_mmap_region){ .type=MEM_AREA_TEE_RAM,//region的內存類型 .region_size=SMALL_PAGE_SIZE,//內存粒度 .pa=start, .va=start, .size=len, .attr=core_mmu_type_to_attr(MEM_AREA_IDENTITY_MAP_RX), }; COMPILE_TIME_ASSERT(CFG_MMAP_REGIONS>=13); //初始化內存信息表,即記錄下各region的PA/VA,用來PV/VA轉換 //后面也會根據這些信息對頁表進行初始化 offs=init_mem_map(tmp_mmap,ARRAY_SIZE(static_memory_map),seed); check_mem_map(tmp_mmap); core_init_mmu(tmp_mmap);//初始化頁表,進行內存映射 dump_xlat_table(0x0,1); core_init_mmu_regs(cfg);//記錄頁表基址,用來設置TTBR0 cfg->load_offset=offs; memcpy(static_memory_map,tmp_mmap,sizeof(static_memory_map)); }
上面函數首先調用init_mem_map初始化一個內存信息表,記錄下各Region的PA和VA,此表用來物理地址和虛擬地址轉換,后面頁表初始化時也會根據此表進行填充。
staticunsignedlonginit_mem_map(structtee_mmap_region*memory_map, size_tnum_elems,unsignedlongseed) { /* *@id_map_startand@id_map_enddescribesaphysicalmemoryrange *thatmustbemappedRead-OnlyeXecutableatidenticalvirtual *addresses. */ vaddr_tid_map_start=(vaddr_t)__identity_map_init_start; vaddr_tid_map_end=(vaddr_t)__identity_map_init_end; unsignedlongoffs=0; size_tlast=0; //根據已注冊的物理地址空間信息來設置memory_map中tee_mmap_region的物理地址范圍(即PA、SIZE) last=collect_mem_ranges(memory_map,num_elems); //設置memory_map中tee_mmap_region的region_size(內存粒度) //如果是tee側的安全內存,則設置region_size為SMALL_PAGE_SIZE(4K) assign_mem_granularity(memory_map); /* *Toeasemappingandloweruseofxlattables,sortmapping *descriptionmovingsmall-pageregionsafterthepgdirregions. */ qsort(memory_map,last,sizeof(structtee_mmap_region), cmp_init_mem_map); //添加一個MEM_AREA_PAGER_VASPACE類型的tee_mmap_region add_pager_vaspace(memory_map,num_elems,&last); if(IS_ENABLED(CFG_CORE_ASLR)&&seed){ //如果開啟了ASLR,則將安全內存起始地址加上一個隨機值 vaddr_tbase_addr=TEE_RAM_START+seed; constunsignedintva_width=get_va_width(); constvaddr_tva_mask=GENMASK_64(va_width-1, SMALL_PAGE_SHIFT); vaddr_tba=base_addr; size_tn=0; for(n=0;n3;?n++)?{ ????????????if?(n) ????????????????ba?=?base_addr?^?BIT64(va_width?-?n); ????????????ba?&=?va_mask;??//?得到一個有效的VA,按頁對齊并且高位無效位清零 ????????????if?(assign_mem_va(ba,?memory_map)?&&?//?設置memory_map中PA對應的VA為ba,已經隨機化 ????????????????mem_map_add_id_map(memory_map,?num_elems,?&last,? ???????????????????????????id_map_start,?id_map_end))?{?////?向memory_map數組中添加一個region,?PA為id_map_start ????????????????offs?=?ba?-?TEE_RAM_START; ????????????????DMSG("Mapping?core?at?%#"PRIxVA"?offs?%#lx", ?????????????????????ba,?offs); ????????????????goto?out; ????????????}?else?{ ????????????????DMSG("Failed?to?map?core?at?%#"PRIxVA,?ba); ????????????} ????????} ????????EMSG("Failed?to?map?core?with?seed?%#lx",?seed); ????} ????//?未開啟ASLR,則設置memory_map中PA對應的VA為TEE_RAM_START,即PA==VA ????//?注意,va和size必須以region_size對齊,memory_map中region的PA可能不是連續的, ????//?但是VA地址空間是連續的 ????if?(!assign_mem_va(TEE_RAM_START,?memory_map)) ????????panic(); out: ????qsort(memory_map,?last,?sizeof(struct?tee_mmap_region), ??????????cmp_mmap_by_lower_va); ????dump_mmap_table(memory_map); ????return?offs; }
其中,collect_mem_ranges根據編譯時保存到phys_mem_map節中物理內存信息來設置memory_map中tee_mmap_region的物理地址范圍 。
//根據已注冊的物理地址空間信息設置memory_map數組中tee_mmap_region的物理地址范圍 staticsize_tcollect_mem_ranges(structtee_mmap_region*memory_map, size_tnum_elems) { conststructcore_mmu_phys_mem*mem=NULL; size_tlast=0; //根據phys_mem_map設置memory_map(用于記錄region的PA/VA的對應關系)的PA、SIZE和attr //phys_mem_map_begin是phys_mem_map數組的起始地址 for(mem=phys_mem_map_begin;mem
通過 register_phys_mem 這個宏將TEE、非安全的共享內存的物理地址空間編譯到phys_mem_map這個section。
register_phys_mem(MEM_AREA_TEE_RAM,TEE_RAM_START,TEE_RAM_PH_SIZE); register_phys_mem(MEM_AREA_TA_RAM,TA_RAM_START,TA_RAM_SIZE); register_phys_mem(MEM_AREA_NSEC_SHM,TEE_SHMEM_START,TEE_SHMEM_SIZE); register_sdp_mem(CFG_TEE_SDP_MEM_BASE,CFG_TEE_SDP_MEM_SIZE); register_phys_mem_ul(MEM_AREA_TEE_RAM_RW,VCORE_UNPG_RW_PA,VCORE_UNPG_RW_SZ); .....
register_phys_mem這個宏使用關鍵字”section”將修飾的變量按照core_mmu_phys_mem結構體編譯到phys_mem_map這個section中。
phys_mem_map_begin指向phys_mem_map這個section的起始地址 。
collect_mem_ranges會根據這個section的信息初始化static_memory_map內存信息數組,這個數組用來 記錄各region的PA、VA、內存屬性、地址空間范圍等信息。
#defineregister_phys_mem(type,addr,size) __register_memory(#addr,(type),(addr),(size), phys_mem_map) #define__register_memory(_name,_type,_addr,_size,_section) SCATTERED_ARRAY_DEFINE_ITEM(_section,structcore_mmu_phys_mem)= {.name=(_name),.type=(_type),.addr=(_addr), .size=(_size)}
值得注意的是,上面注冊的TEE_RAM_START開始的物理地址空間就是TZC-380配置的Region 2,即安全內存地址空間。
#defineTEE_RAM_STARTTZDRAM_BASE #defineTEE_RAM_PH_SIZETEE_RAM_VA_SIZE #defineTZDRAM_BASECFG_TZDRAM_START #defineTZDRAM_SIZECFG_TZDRAM_SIZE
接下來, core_init_mmu調用core_init_mmu_ptn來對整個注冊的內存地址空間進行VA到PA的映射,即根據PA和VA填充頁表。
voidcore_init_mmu_prtn(structmmu_partition*prtn,structtee_mmap_region*mm) { size_tn; assert(prtn&&mm); for(n=0;!core_mmap_is_end_of_table(mm+n);n++){ debug_print("%010"PRIxVA"%010"PRIxPA"%10zx%x", mm[n].va,mm[n].pa,mm[n].size,mm[n].attr); if(!IS_PAGE_ALIGNED(mm[n].pa)||!IS_PAGE_ALIGNED(mm[n].size)) panic("unalignedregion"); } /*Cleartablebeforeuse*/ memset(prtn->l1_tables,0,sizeof(l1_xlation_table)); for(n=0;!core_mmap_is_end_of_table(mm+n);n++) //如果不是動態虛擬地址空間,則進行填充頁表(映射內存) if(!core_mmu_is_dynamic_vaspace(mm+n)) //根據PA/VA填充頁表,即做內存映射 core_mmu_map_region(prtn,mm+n); /* *Primarymappingtableisreadyatindex`get_core_pos()` *whosevaluemaynotbeZERO.Takethisindexascopysource. */ //根據已設置的頁表設置所有核的頁表 for(n=0;nl1_tables[0][n], prtn->l1_tables[0][get_core_pos()], XLAT_ENTRY_SIZE*NUM_L1_ENTRIES); } }
到這里,TEE側OS已經完成了對物理內存的映射,包括安全內存和共享內存。在開啟分頁后,TEEOS就可以訪問這些虛擬內存地址空間了。
CODE-安全側地址校驗
下面以符合GP規范的TEE接口為例,簡單介紹下CA和TA的通信流程:
篇幅所限,這里僅分析Secure World側的調用流程,重點關注TA_InvokeCommandEntryPoint調用流程,此函數用來處理所有來自Normal World側的請求,安全側可信應用的漏洞挖掘也是從這個函數開始入手,這里我們只分析地址校驗相關流程。
?1.在TEEC_OpenSession中會去加載TA的elf文件,并設置相應的函數操作表,最終調用目標TA的TA_OpenSessionEntryPoint。__tee_entry_std
-->entry_open_session
-->tee_ta_open_session
-->tee_ta_init_session-->tee_ta_init_user_session-->set_ta_ctx_ops
-->ctx->ops->enter_open_session(user_ta_enter_open_session)
-->user_ta_enter
-->tee_mmu_map_param
-->thread_enter_user_mode
-->__thread_enter_user_mode//返回到S_EL0,調用目標TA的TA_OpenSessionEntryPoint?2.TA_InvokeCommandEntryPoint調用流程如下,在此函數中會對REE傳入的地址進行校驗。__tee_entry_std
-->entry_invoke_command
-->copy_in_param
-->set_tmem_param//如果是memref類型,則調用set_tmem_param分配共享內存
-->msg_param_mobj_from_nocontig
-->mobj_mapped_shm_alloc
-->mobj_reg_shm_alloc//最終會調用core_pbuf_is來檢查RRE傳入的PA是否在非安全內存地址范圍內
-->tee_ta_get_session
-->tee_ta_invoke_command
-->check_params
-->sess->ctx->ops->enter_invoke_cmd(user_ta_enter_invoke_cmd)
-->user_ta_enter
-->tee_mmu_map_param//映射用戶空間地址(S_EL0)
-->tee_ta_push_current_session
-->thread_enter_user_mode//返回S_EL0相應的TA中執行TA_InvokeCommandEntryPoint通過以上代碼分析可知,在調用TA的TA_InvokeCommandEntryPoint函數之前會對REE側傳入的參數類型進行檢查 ,在TA代碼中使用REE傳入參數作為內存地址的場景下,如果未校驗對應的參數類型或者參數類型為TEEC_VALUE_INPUT(與實際使用參數類型不匹配),則會繞過上面core_pbuf_is對REE傳入PA的檢查 ,可以傳入任意值,這個值可以為安全內存PA,這樣就可以導致以S_EL0權限讀寫任意安全內存。
總結
TEE作為可信執行環境,通常用于運行處理指紋、人臉、PIN碼等關鍵敏感信息的可信應用,即使手機被ROOT,攻擊者也無法獲取這些敏感數據。
因此TEE側程序的安全至關重要,本文深入分析了TRUSTZONE物理內存隔離、TEEOS內存管理及TEE側對REE傳入地址的校驗。
在了解了這些原理之后,我們就可以進行漏洞挖掘了, 當然也能寫出簡單有效的FUZZ工具。只有對漏洞原理、攻擊方法進行深入的理解 ,才能進行有效的防御。
FUZZ是一個模糊測試工具,用于在漏洞挖掘過程中進行重要的一步。它能夠檢查常見的漏洞,如緩沖區溢出、格式串漏洞、整數溢出等。
模糊測試是一種通過輸入大量隨機產生的數據來檢測程序異常的自動化測試方法。在模糊測試中,測試用例是隨機生成的,而不是手動創建的。這種方法可以幫助發現一些常見的問題,如邊界條件錯誤、類型錯誤、內存泄漏等。
FUZZ-COV是另一個流行的模糊測試工具,它使用覆蓋引導技術來提高模糊測試的效率。該工具能夠檢測出更多的漏洞,并且比其他工具更快。
除了FUZZ和FUZZ-COV,還有其他一些模糊測試工具,如BETA、BAP和LibFuzzer。這些工具在設計和實現上略有不同,但它們的目標是相同的,即發現程序中的漏洞。
審核編輯:湯梓紅
-
控制器
+關注
關注
112文章
15334瀏覽量
172344 -
寄存器
+關注
關注
30文章
5141瀏覽量
118055 -
cpu
+關注
關注
68文章
10509瀏覽量
207139 -
內存管理
+關注
關注
0文章
163瀏覽量
14065
原文標題:參考
文章出處:【微信號:談思實驗室,微信公眾號:談思實驗室】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
相關推薦
評論