作者: wzt
本文為作者投稿,Seebug Paper 期待你的分享,凡經采用即有禮品相送! 投稿郵箱:paper@seebug.org
Ppl代碼初始化
ppl物理內存初始化
內核在啟動階段調用pmap_bootstrap函數初始化物理內存布局。
pmap_bootstrap:
LDR X20, [X27,#_avail_start@PAGEOFF]
MOV X0, X20
BL _phystokv
MOV X21, X0
ADRP X8, #_pmap_array_begin@PAGE
STR X0, [X8,#_pmap_array_begin@PAGEOFF]
ADRP X19, #_pmap_array@PAGE
STR X0, [X19,#_pmap_array@PAGEOFF]
ADRP X24, #_pmap_max_asids@PAGE
LDR W8, [X24,#_pmap_max_asids@PAGEOFF]
MOV W9, #0x108
MOV W10, #0x3FFF
MADD X8, X8, X9, X10
AND X8, X8, #0x3FFFFFFC000
ADD X0, X8, X20
STR X0, [X27,#_avail_start@PAGEOFF]
BL _phystokv
ADRP X8, #_pmap_array_end@PAGE
STR X0, [X8,#_pmap_array_end@PAGEOFF]
_avail_start保存的是當前空閑物理內存的物理地址,轉換成虛擬地址后,保存在_pmap_array_begin和_pmap_array變量中,然后從此物理地址劃出_pmap_max_asids字節大小的區域給_pmap_array,它是一個struct pmap數組,在ppl的實現中,內核以及每個進程都有單獨的一個struct pmap結構。在非ppl的版本中,只有內核使用struct map。
ADRP X8, #_pmap_array_count@PAGE
ADRL X10, _pmap_free_list_lock
STR X9, [X8,#_pmap_array_count@PAGEOFF]
STR X23, [X10,#(qword_FFFFFFF00981CA88 - 0xFFFFFFF00981CA80)]
STR XZR, [X10]
LDR X9, [X8,#_pmap_array_count@PAGEOFF]
計算_pmap_array_count大小。
MOV X9, #0
MOV X10, #0
MOV X12, #0
LDR X11, [X19,#_pmap_array@PAGEOFF]
STR X12, [X11,X9]
LDR X11, [X19,#_pmap_array@PAGEOFF]
ADD X12, X11, X9
ADD X10, X10, #1
LDR X13, [X8,#_pmap_array_count@PAGEOFF]
ADD X9, X9, #0x108
CMP X10, X13
B.CC loc_FFFFFFF007B5E820
ADD X8, X11, X9
SUB X8, X8, #0x108
B loc_FFFFFFF007B5E850
MOV X8, #0
ADRP X9, #_pmap_free_list@PAGE
STR X8, [X9,#_pmap_free_list@PAGEOFF]
初始化_pmap_free_list鏈表,循環讓_pmap_array數組中的每個元素依次指向前一個節點,以后ppl為每個進程分配struct pmap結構體時就從這個鏈表分配。
下面用類似的算法初始化_pmap_ledger_ptr_array數組,這里不在贅述。
然后開始初始化ppl使用的stack信息:
ADRP X8, #_pmap_stacks_start@PAGE
LDR X8, [X8,#_pmap_stacks_start@PAGEOFF]
ADD X26, X8, #4,LSL#12
ADRP X8, #_pmap_stacks_start_pa@PAGE
STR X20, [X8,#_pmap_stacks_start_pa@PAGEOFF]
_pmap_stacks_startv保存的是stack的虛擬地址,_pmap_stacks_start_pa保存的是stack的物理地址,后面修改kernel_map->tte的l3頁表,進行虛擬地址到物理地址的映射。
MOV X28, #0x20000000000603
ADRL X21, _pmap_cpu_data_array
MOV W25, #0xFFFFFFFF
B loc_FFFFFFF007B5E92C
MOV W8, #0x180
MADD X8, X19, X8, X21
STR W25, [X8,#0x40]
STR WZR, [X8,#0x18]
STR X24, [X8,#8]
ADD X26, X26, #8,LSL#12
ADD X19, X19, #1
CMP X19, #6
B.EQ loc_FFFFFFF007B5E9B8
ADD X24, X26, #4,LSL#12
MOV X23, X26
MOV X8, #0xFFFFFFFFFFFFBFFF
CMP X26, X8
B. HI loc_FFFFFFF007B5E908
依次設置每個cpu的_pmap_cpu_data結構體,STR X24, [X8,#8],將棧地址保存在0x8偏移處。每個cpu的ppl stack都是4k,之間在隔離著一個4k的guard page。
后面的代碼繼續初始化_ppl_cpu_save_area,它保存的是ppl進入EL1_Guard level異常處理時保存的全部寄存器區域。
下面給出xnu物理內存布局圖,請原諒我的懶惰,不想畫漂亮的圖形。

pp_attr_table與pv_head_table
XNU的物理內存管理模型,增加了兩個結構體用于輔助物理頁的管理。
pp_attr_t代表的是一個物理頁的屬性,在XNU的source code里可以看到其定義:
osfmk/arm/pmap.c
typedef u_int16_t pp_attr_t;
\#define PP_ATTR_WIMG_MASK 0x003F
\#define PP_ATTR_WIMG(x) ((x) & PP_ATTR_WIMG_MASK)
\#define PP_ATTR_REFERENCED 0x0040
\#define PP_ATTR_MODIFIED 0x0080
\#define PP_ATTR_INTERNAL 0x0100
\#define PP_ATTR_REUSABLE 0x0200
\#define PP_ATTR_ALTACCT 0x0400
\#define PP_ATTR_NOENCRYPT 0x0800
\#define PP_ATTR_REFFAULT 0x1000
\#define PP_ATTR_MODFAULT 0x2000
\#if XNU_MONITOR
/*
\* Denotes that a page is owned by the PPL. This is modified/checked with the
\* PVH lock held, to avoid ownership related races. This does not need to be a
\* PP_ATTR bit (as we have the lock), but for now this is a convenient place to
\* put the bit.
*/
\#define PP_ATTR_MONITOR 0x4000
/*
\* Denotes that a page *cannot* be owned by the PPL. This is required in order
\* to temporarily 'pin' kernel pages that are used to store PPL output parameters.
\* Otherwise a malicious or buggy caller could pass PPL-owned memory for these
\* parameters and in so doing stage a write gadget against the PPL.
*/
\#define PP_ATTR_NO_MONITOR 0x8000
對于ppl來講,增加了兩個屬性PP_ATTR_MONITOR和PP_ATTR_NO_MONITOR,ios使用相同的值,后面會看到。
每個物理頁在內核中有兩種用處,第一個物理頁保存的是頁表內容,第二個就是保存內核用到的其他數據結構,在XNU的物理內存管理模型中,使用struct pv_entry結構體保存l3頁表項的地址,可以提高物理內存與虛擬內存相互轉化的效率。使用struct pt_desc結構體描述一個頁表屬性,比如l1、l2、l3table的屬性。對于ppl,會用到pv_head_table對給定的一個物理頁上鎖。
Pv_head_table的內存布局如下,請再次原諒我的懶惰。

EL1_Guard level初始化
在初始化完ppl的物理內存布局后,內核在啟動的下一階段會調用_bootstrap_instructions繼續執行EL1_Guard level的初始化。
__start->_start_first_cpu->_arm_init->_arm_vm_init->_bootstrap_instructions
_bootstrap_instructions:
關閉mmu
S3_6_C15_C1_00x1
S3_6_C15_C1_10x1
S3_6_C15_C1_50x2010000030100000
S3_6_C15_C1_60x2020000030200000
S3_6_C15_C1_70x2020a500f020f000
S3_6_C15_C3_00x2020a505f020f0f0
S3_6_C15_C1_20x1
S3_6_C15_C8_1_gxf_bootstrap_handler(虛擬地址)
S3_6_C15_C8_2_gtr_deadloop(虛擬地址)
S3_6_C15_C1_20x0
開啟mmu
在這個函數里,可以看到蘋果cpu加入了幾個新的系統寄存器, 通過分析代碼邏輯可以推測出S3_6_C15_C1_2寄存器應該是個執行上鎖和解鎖的功能。gxf_bootstrap_handler函數地址存入了S3_6_C15_C8_1寄存器,_gtr_deadloop函數存入了S3_6_C15_C8_2寄存器。在整個kernelcache代碼段里都沒有搜索到對_gxf_bootstrap_handler函數的直接引用,因此可以推斷出S3_6_C15_C8_1寄存器保存的地址應該是cpu進入EL1_Guard level時首先要執行的函數。在另外一個初始化路徑中也可以看到相同的ppl初始化代碼。
__start->_start_first_cpu->_arm_init->_cpu_machine_idle_init->_start_cpu->start_cpu
start_cpu:
S3_6_C15_C1_00x1
S3_6_C15_C1_10x1
S3_6_C15_C3_00x2020a506f020f0e0
S3_6_C15_C1_50x2010000030100000
S3_6_C15_C1_60x2020000030200000
S3_6_C15_C1_70x2020a500f020f000
S3_6_C15_C1_20x1
S3_6_C15_C8_1_gxf_bootstrap_handler(虛擬地址)
S3_6_C15_C8_2_gtr_deadloop(虛擬地址)
S3_6_C15_C1_20x0
注意這兩個初始化函數只是對S3_6_C15_C8_1寄存器進行了設置,并沒有調用它。在內核啟動的最后階段有如下調用代碼:
_kernel_bootstrap_thread->machine_lockdown
ADRP X8, #_pmap_ppl_locked_down@PAGE
MOV W19, #1
STR W19, [X8,#_pmap_ppl_locked_down@PAGEOFF]
BL _gxf_enable
首先對_pmap_ppl_locked_down變量設置為1,這個狀態表示ppl已經初始化完畢,el1代碼可以向EL1_Guard level請求服務了。然后調用_gxf_enable:
_gxf_enable:
fffffff008131e90 mov x0, #0x1
fffffff008131e94 msr S3_6_C15_C1_2, x0
fffffff008131e98 .long 0x00201420
在對S3_6_C15_C1_2寄存器賦值后,出現了0x00201420這個反匯編器無法識別的指令。那么這個有可能的兩種情況,第一個假設0x00201420不是蘋果cpu的新增指令,那么當cpu進行執行時,會產生exception異常,歷史上,PAX團隊曾經在異常處理代碼里模擬了NX在32位處理器上的軟件實現,但筆者翻遍了異常處理邏輯的所有代碼,也沒有發現對0x00201420這個數據或指令的特殊處理,因此可以斷定0x00201420是蘋果cpu新增的指令,在后面的reverse中,可以發現它是進入EL1_Guard level的指令,0x00201400則是退出EL1_Guard level的指令。
在前面的分析中講到S3_6_C15_C8_1寄存器保存的地址是進入EL1_Guard level時首先要執行的地址,在前面的初始化時被設置為了_gxf_bootstrap_handler。
_gxf_bootstrap_handler ; DATA XREF: _bootstrap_instructions+9C↑o
MRS X0, #6, c15, c8, #3
TBNZ W0, #0, loc_FFFFFFF009811660
ADRL X0, _gxf_ppl_entry_handler ;
MSR #6, c15, c8, #1, X0
ADRL X0, _GuardedExceptionVectorsBase
MSR #0, c12, c0, #0, X0
首先判斷S3_6_C15_C8_3寄存器的值是否為0,如果為0,就一直處于循環之中,可以推測出S3_6_C15_C8_3寄存器作用是進入EL1_Guard level的鎖機制。然后將_gxf_ppl_entry_handler函數重新存入了S3_6_C15_C8_1寄存器,也就是說下次進入EL1_Guard level時將會執行_gxf_ppl_entry_handler函數。將_GuardedExceptionVectorsBase地址寫入VBAR_EL1寄存器,也就是說當cpu在EL1_Guard level時,_GuardedExceptionVectorsBase函數負責其異常處理邏輯。
MRS X1, #0, c0, c0, #5
UBFX X2, X1, #8, #8
ADRL X3, _cluster_offsets
LDR X2, [X3,X2,LSL#3]
AND X1, X1, #0xFF
ADD X1, X1, X2
ADRL X2, _pmap_cpu_data_array
CMP X1, #6
B.CS loc_FFFFFFF0098116A8
MOV X3, #0x180
MADD X1, X1, X3, X2
首先從MPIDR_EL1寄存器提取了cpu的cluster id和cpu id,算出在_pmap_cpu_data_array數組中的索引。_pmap_cpu_data_array是一個struct pmap_cpu_data類型的數組。在XNU的source code中有如下定義:
struct pmap_cpu_data {
\#if XNU_MONITOR
void * ppl_kern_saved_sp;
void * ppl_stack;
arm_context_t * save_area;
unsigned int ppl_state;
\#endif
}
LDR X1, [X1,#0x10]
MOV SP, X1
X1保存的是當前cpu的struct pmap_cpu_data結構體指針,0x10偏移為save_area,它指向了arm_context_t類型的內存區域,將這個內存區域設置為EL1_Guard level的sp地址。在從el1進入EL1_Guard level時,將全部的寄存器保存在這個區域。
ADRP X1, #_pmap_ppl_locked_down@PAGE
LDR X2, [X1,#_pmap_ppl_locked_down@PAGEOFF]
CBZ X2, loc_FFFFFFF0098116C0
接著判斷_pmap_ppl_locked_down值是否為0,如果為0,則一直循環檢查下去。在前面的machine_lockdown函數中已經將_pmap_ppl_locked_down設置為了1,所以會繼續運行下面的代碼:
MOVK X0, #0x2020,LSL#48
MOVK X0, #0xA506,LSL#32
MOVK X0, #0xF020,LSL#16
MOVK X0, #0xF0E0
MSR #6, c15, c3, #0, X0
MOVK X0, #0,LSL#48
MOVK X0, #0,LSL#32
MOVK X0, #0,LSL#16
MOVK X0, #5
MSR #6, c15, c1, #2, X0
ADRL X0, _invalid_ttep
LDR X0, [X0]
MSR #0, c2, c0, #0, X0
將0x2020a506f020f0e0賦值給了S3_6_C15_C3_0寄存器,將0x5賦值給了S3_6_C15_C1_2寄存器,將_invalid_ttep頁表地址賦值給了TTBR0_EL1。
SYS #0, c8, c3, #0
DSB ISH
ISB
DCD 0x201400
執行0x201400指令,退出EL1_Guard level。
至此ppl全部初始化已經完成, el1代碼可以請求EL1_Guard level的服務了。
Ppl進入與退出
Ppl給el1代碼提供了_gxf_ppl_enter接口用于請求其服務。
_arm_fast_fault_ppl:
mov x15, #0x0
b _gxf_ppl_enter
_arm_force_fast_fault_ppl:
mov x15, #0x1
b _gxf_ppl_enter
_mapping_free_prime_ppl:
mov x15, #0x2
b _gxf_ppl_enter
這里只羅列部分函數,x15保存的是ppl服務表的索引。
_gxf_ppl_enter:
fffffff008131c3c pacibsp
fffffff008131c40 stp x20, x21, [sp, #-0x20]! ; Latency: 6
fffffff008131c44 stp x29, x30, [sp, #0x10] ; Latency: 6
fffffff008131c48 add x29, sp, #0x10
fffffff008131c4c mrs x9, TPIDR_EL1
fffffff008131c50 ldr w10, [x9, #0x500] ; Latency: 4
fffffff008131c54 add w10, w10, #0x1
fffffff008131c58 str w10, [x9, #0x500] ; Latency: 4
fffffff008131c5c adrp x14, 5867 ; _pmap_ppl_locked_down
fffffff008131c60 add x14, x14, #0x130
fffffff008131c64 ldr x14, [x14] ; Latency: 4
fffffff008131c68 cbz x14, _ppl_bootstrap_dispatch
fffffff008131c6c mrs x14, S3_6_C15_C8_0
fffffff008131c70 cmp x14, #0x0
fffffff008131c74 b.ne 0xfffffff008131c74
fffffff008131c78 mov w10, #0x0
fffffff008131c7c .long 0x00201420
首先檢查_pmap_ppl_locked_down是否為0,如果為0,說明ppl并沒有初始化完成,直接跳轉到_ppl_bootstrap_dispatch函數:
CMP X15, #0x47 ; 'G'
B.CS loc_FFFFFFF008131DD4
ADRL X9, _ppl_handler_table
ADD X9, X9, X15,LSL#3
X15保存的是_ppl_handler_table的索引,如果大于0x47,會觸發panic。
LDR X10, [X9]
BLRAA X10, X9
跳轉到_ppl_handler_table[x15<<3]去執行具體的服務函數。
MOV X20, X0
BL __enable_preemption
MOV X0, X20
LDP X29, X30, [SP,#arg_10]
LDP X20, X21, [SP+arg_0],#0x20
RETAB
在沒有進入EL1_Guard level情況下,直接通過ret返回。
如果_pmap_ppl_locked_down為1,則繼續如下代碼:
fffffff008131c78 mov w10, #0x0
fffffff008131c7c .long 0x00201420
將w10設置為0, 這個很重要,后面會講到。
執行0x00201420進入EL1_Guard level, 前面在初始化時講到cpu進入EL1_Guard level時,會執行S3_6_C15_C8_1寄存器指定的地址,也就是_gxf_ppl_entry_handler。
MSR #5, #0
MRS X9, #6, c15, c8, #3 ; S3_6_C15_C8_3
TBNZ W9, #0, loc_FFFFFFF008131C88
MRS X12, #6, c15, c9, #1 ; S3_6_C15_C9_1
MSR #0, c13, c0, #4, X12 ; TPIDR_EL1
MRS X20, #0, c4, c0, #0 ; SPSR_EL1
AND X20, X20, #0x3C0 ; mask FIQ; SET EL3t
B _ppl_trampoline_start
首先將SPSel設置為0,然后判斷S3_6_C15_C8_3是否為1,否則一直循環等待,這再次證明S3_6_C15_C8_3寄存器為進入EL1_Guard level的鎖機制。然后將S3_6_C15_C9_1寄存器的值寫入TPIDR_EL1,說明它保存的是請求服務時的線程信息。將SPSR_EL1設置為mask FIQ; SET EL3t,為何是EL3t? 接著跳入_ppl_trampoline_start:
MOVK X14, #0x2020,LSL#48
MOVK X14, #0xA506,LSL#32
MOVK X14, #0xF020,LSL#16
MOVK X14, #0xF0E0 ; 0x2020a506f020f0e0
MRS X21, #6, c15, c3, #0
CMP X14, X21
B. NE loc_FFFFFFF00980D9AC
首先判斷S3_6_C15_C3_0寄存器的值是否為0x2020a506f020f0e0,這個寄存器的作用筆者尚未搞清楚。
CMP X15, #0x47 ; 'G'
B.CS loc_FFFFFFF00980D9AC
再次判斷x15合法范圍。
MRS X12, #0, c0, c0, #5 ; MPIDR_EL1
UBFX X13, X12, #8, #8 ; get cluster id
ADRL X14, _cluster_offsets
LDR X13, [X14,X13,LSL#3] ; _cluster_offsets[index<<3]
AND X12, X12, #0xFF
ADD X12, X12, X13
ADRL X13, _pmap_cpu_data_array
CMP X12, #6
C. CS loc_FFFFFFF00980D92C
MOV X14, #0x180
MADD X12, X12, X14, X13
得到當前cpu的struct pmap_cpu_data結構體指針。
LDR W9, [X12,#0x18] ; _pmap_cpu_data->ppl_state
W9保存的是ppl的當前狀態ppl_state,ppl的狀態有3個定義:
PPL_STATE_KERNEL
PPL_STATE_DISPATCH
PPL_STATE_EXCEPTION
_ppl_trampoline_start這個函數可以由ppl_enter由el1代碼主動調用,也可以由EL1_Guard level的異常處理調用,在后面會分析到。
CMP W9, #0 ; PPL_STATE_KERNEL
B. EQ loc_FFFFFFF00980D970
W9為0,即PPL_STATE_KERNEL,表示可以請求ppl服務。
loc_FFFFFFF00980D970 ; CODE XREF: _ppl_trampoline_start+60↑j
CMP W10, #0
判斷w10是否為0,注意在前面的ppl_enter路徑中,已經將w10設置為了0,所以代碼可以進行前進。
B.NE loc_FFFFFFF00980D9AC
MOV W13, #1
STR W13, [X12,#0x18] ; enter PPL_STATE_DISPATCH mode
更改ppl_state狀態為1,即 PPL_STATE_DISPATCH,表示ppl正處在服務派發狀態中。
LDR X9, [X12,#8] ; _pmap_cpu_data->ppl_stack
MOV X21, SP
MOV SP, X9
_pmap_cpu_data->ppl_stack保存的是ppl服務要使用的stack區域。注意_pmap_cpu_data->save_area保存的是進入EL1_Guard level異常處理時保存全部寄存器用的棧區域。
STR X21, [X12] ; save old el1 sp
保存el1原來的sp到_pmap_cpu_data->ppl_kern_saved_sp,以后退出ppl服務時還要恢復原來的stack指針。
ADRL X9, _ppl_handler_table
ADD X9, X9, X15,LSL#3 ; _ppl_handler_table[index<<3]
LDR X10, [X9]
B _ppl_dispatch
X10保存了具體的服務函數地址_ppl_handler_table[x15<<3]``,然后跳轉到_ppl_dispatch執行,在屏蔽了一些列中斷后,進行執行:
BLRAA X10, X9
調用具體的ppl服務函數。
__PPLTEXT:__text:FFFFFFF00980D9B8
MOV X15, #0
MOV SP, X21
MOV X10, X20
STR XZR, [X12]
LDR W9, [X12,#0x18] ; _pmap_cpu_data->ppl_state
CMP W9, #1 ; PPL_STATE_DISPATCH
B.NE loc_FFFFFFF00980D9D0
MOV W9, #0
STR W9, [X12,#0x18] ; PPL_STATE_KERNEL
服務執行完之后,要把ppl_state再次設置 PPL_STATE_KERNEL。
B ppl_return_to_kernel_mode
最后調用ppl_return_to_kernel_mode退出EL1_Guard level,返回el1代碼。
ADRL X14, ppl_exit
MSR #0, c4, c0, #1, X14 ; ELR_EL1
MRS X14, #6, c15, c8, #3
AND X14, X14, #0xFFFFFFFFFFFFFFFE
MSR #6, c15, c8, #3, X14 ; S3_6_C15_C8_3
MRS X14, #0, c4, c0, #0 ; SPSR_EL1
AND X14, X14, #0xFFFFFFFFFFFFFC3F
ORR X14, X14, X10
MSR #0, c4, c0, #0, X14 ; SPSR_EL1 -> EL3h
MRS X14, #0, c13, c0, #4 ; TPIDR_EL1
MSR #6, c15, c9, #1, X14 ; S3_6_C15_C9_1
DCQ 0x201400
首先將ELR_EL1設置為ppl_exit,然后將S3_6_C15_C8_3的最低位清0,然后將SPSR_EL1設置為 EL3h,注意SPSR_EL1的第2個bit設置為了1,在arm手冊里這個bit是保留的,說明蘋果處理器使用了這個bit代表ppl的執行狀態,但是很奇怪為啥都設置為EL3h,而不是EL1h?隨后將TPIDR_EL1值保存在了S3_6_C15_C9_1寄存器。
最后執行0x201400退出EL1_Guard level,由于ELR_EL1寄存器保存的是ppl_exit,cpu跳轉到ppl_exit繼續執行, ppl_exit恢復之前屏蔽的中斷,然后使用ret指令返回。
上面分析的是一個正常通過ppl_enter請求ppl服務的路徑。下面繼續返回_ppl_trampoline_start代碼。
CMP W9, #1 ; PPL_STATE_DISPATCH
C. EQ loc_FFFFFFF00980D9A4 ; _pmap_cpu_data_array->ppl_kern_saved_sp
如果當前ppl_state狀態為PPL_STATE_DISPATCH,表示有其他cpu正在請求ppl服務,因此恢復之前通過_pmap_cpu_data_array->ppl_kern_saved_sp保存的棧,然后退出EL1_Guard level,前面的路徑已經分析過了。
CMP W9, #3 ; PPL_STATE_EXCEPTION
B. NE loc_FFFFFFF00980D9AC
如果ppl_state也不是PPL_STATE_EXCEPTION,則直接退出EL1_Guard level。
CMP W10, #3 ; PPL_STATE_EXCEPTION
C. NE loc_FFFFFFF00980D9AC
MOV W9, #1
STR W9, [X12,#0x18] ; PPL_STATE_DISPATCH
LDR X0, [X12,#0x10] ; _pmap_cpu_data_array->save_area
MOV SP, X0
B _return_to_ppl
將sp設置為_pmap_cpu_data_array->save_area,_return_to_ppl要使用這個棧恢復之前進入EL1_Guard level異常處理時保存的全部寄存器值,恢復cpsr, ELR_EL1設置原來的調用地址,恢復bp,sp,通過eret返回原來的el1路徑中。
為啥要判斷w10的值是否為3?這個路徑表示的是EL1_Guard level異常處理路徑中要請求ppl服務。
_GuardedExceptionVectorsBase是EL1_Guard level異常處理地址,我們看到它只對0x000的offset使用了有效函數,表明它只處理來自同層current level的異常處理,再次證明EL1_Guard level也是在EL1 level上。
__PPLTEXT:__text:FFFFFFF0098114B0 gtr_sync:
MOV X26, #1
ADRL X1, _fleh_synchronous ; ESR_EL1
MSR #0, c4, c0, #1, X1 ; ELR_EL1
可以看到gtr_sync把_fleh_synchronous賦值給了ELR_EL1, x26設置為1很關鍵,這是一個標識,后面會講到。
MOV X0, SP
DCD 0x201400
隨后gtr_sync使用0x201400指令退出了EL1_Guard level,轉而執行el1 _fleh_synchronous函數,這是el1內核異常處理的主要函數,這說明EL1_Guard level的主要處理邏輯會使用el1內核代碼,這也就解釋了ppl為什么能保護內核態和用戶態的數據。以前我們在使用el2做保護時,只能保護el1的數據,因為el1的使用的代碼和數據都已經是映射好的,而el0層用戶的代碼和數據會涉及到很多缺頁異常處理的邏輯,比如頁表不存在,或者頁表被交換到磁盤上,這會使el2的異常處理邏輯非常復雜。EL1_Guard level使用el1內核代碼的處理邏輯就簡化了自身的代碼邏輯,性能也會提升不少。
__TEXT_EXEC:__text:FFFFFFF0081315CC _fleh_synchronous
BL _sleh_synchronous
_fleh_synchronous又調用了_sleh_synchronous,這個函數是異常處理邏輯的主要處理函數。
CMP X26, XZR
B. EQ loc_FFFFFFF00813161C
注意_fleh_synchronous是el1內核代碼的處理邏輯,EL1_Guard level是借用了它的代碼,在前面看到gtr_sync把x26設置為了1,在_fleh_synchronous函數里表示此次請求來自EL1_Guard level。
MOV X15, #0
MOV W10, #3
DCB 0x20, 0x14, 0x20, 0
然后將x15設置為0,w10設置為3,最后調用0x201420再次進入EL1_Guard level,此時就會跳轉到_ppl_trampoline_start的異常處理請求路徑里:
CMP W9, #3 ; PPL_STATE_EXCEPTION
B.NE loc_FFFFFFF00980D9AC
CMP W10, #3 ; PPL_STATE_EXCEPTION
B.NE loc_FFFFFFF00980D9AC
MOV W9, #1
STR W9, [X12,#0x18] ; PPL_STATE_DISPATCH
LDR X0, [X12,#0x10] ; _pmap_cpu_data_array->save_area
MOV SP, X0
B _return_to_ppl
_return_to_ppl恢復EL1_Guard level異常處理時保存的全部寄存器值,恢復cpsr, ELR_EL1設置原來的調用地址,恢復bp,sp,通過eret返回原來的el1路徑中。
ppl服務
_ppl_handler_table數組保存的是ppl服務函數地址。
_ppl_handler_table:
_arm_force_fast_fault_internal
_mapping_free_prime_internal
_phys_attribute_clear_internal
_phys_attribute_set_internal
_pmap_batch_set_cache_attributes_internal
_pmap_change_wiring_internal
_pmap_create_options_internal
_pmap_destroy_internal
_pmap_enter_options_internal
_pmap_find_pa_internal
_pmap_insert_sharedpage_internal
_pmap_is_empty_internal
_pmap_map_cpu_windows_copy_internal
_pmap_mark_page_as_ppl_page_internal
_pmap_nest_internal
_pmap_page_protect_options_internal
_pmap_protect_options_internal
_pmap_query_page_info_internal
_pmap_query_resident_internal
_pmap_reference_internal
_pmap_remove_options_internal
_pmap_return_internal
_pmap_set_cache_attributes_internal
_pmap_set_nested_internal
_pmap_set_process_internal
_pmap_switch_internal
_pmap_switch_user_ttb_internal
_pmap_clear_user_ttb_internal
_pmap_unmap_cpu_windows_copy_internal
_pmap_unnest_options_internal
_pmap_footprint_suspend_internal
_pmap_cpu_data_init_internal
_pmap_release_ppl_pages_to_kernel_internal
_pmap_set_jit_entitled_internal
_pmap_load_legacy_trust_cache_internal
_pmap_load_image4_trust_cache_internal
_pmap_is_trust_cache_loaded_internal
_pmap_lookup_in_static_trust_cache_internal
_pmap_lookup_in_loaded_trust_caches_internal
_pmap_cs_cd_register_internal
_pmap_cs_cd_unregister_internal
_pmap_cs_associate_internal
_pmap_cs_lookup_internal
_pmap_cs_check_overlap_internal
_pmap_iommu_init_internal
_pmap_iommu_iovmalloc_internal
_pmap_iommu_map_internal
_pmap_iommu_unmap_internal
_pmap_iommu_iovmfree_internal
_pmap_iommu_ioctl_internal
_pmap_iommu_grant_page_internal
_pmap_update_compressor_page_internal
_pmap_trim_internal
_pmap_ledger_alloc_init_internal
_pmap_ledger_alloc_internal
_pmap_ledger_free_internal
_pmap_sign_user_ptr_internal
_pmap_auth_user_ptr_internal
_phys_attribute_clear_range_internal
可以看到ppl服務代碼是非常復雜的,覆蓋了物理內存和虛擬內存管理的方方面面。
_pmap_mark_page_as_ppl_page_internal
我們先看下如何將一個物理頁標記為ppl使用的物理頁。
_pmap_mark_page_as_ppl_page_internal:
MOV X19, X0
ADRP X23, #_vm_first_phys@PAGE ; x0(pa) > vm_first_phys && x0 < vm_last_phys
LDR X8, [X23,#_vm_first_phys@PAGEOFF]
ADRP X24, #_vm_last_phys@PAGE
LDR X9, [X24,#_vm_last_phys@PAGEOFF]
CMP X8, X0
CCMP X9, X0, #0, LS
C. LS loc_FFFFFFF0097FB5B0
X0保存的是要標記的物理地址pa,然后檢查它是否在_vm_first_phys與_vm_last_phys之間,這是XNU管理的有效物理內存區間。
LDR X11, [X9,#_pp_attr_table@PAGEOFF]
LDRSH W10, [X11,X8,LSL#1]
TBNZ W10, #0x1F, loc_FFFFFFF0097FB580 ;
ORR W12, W10, #0x4000 ; pp_attr_table[pai] & 0x4000
ADD X11, X11, X8,LSL#1
MOV X13, X10
CASALH W13, W12, [X11]
pp_attr_table[pai] &= 0x4000,將pa對應的pp_attr_table進行標記。
LDR X9, [X21,#_pv_head_table@PAGEOFF]
ADD X8, X9, X8,LSL#3
ADD X8, X8, #4
MOV W9, #0x20000000
LDCLRL W9, W8, [X8] ;
_pv_head_table[index] &= 0x20000000
MOV X0, X19
BL _phystokv
ADD X1, X0, #4,LSL#12
MOV W2, #3
MOV W3, #1
BL _pmap_set_range_xprr_perm
在對pp_attr_table和_pv_head_table進行標記后,調用_pmap_set_range_xprr_perm函數,這個函數作用是將虛擬地址va,到va + size之間的所有虛擬地址對應的頁表屬性進行轉換,第三個參數代表的是原來頁表的權限屬性,第四個參數代表的是新的頁表屬性,這個函數用來在內核頁表和ppl頁表之間進行權限轉換。




首先檢查要進行轉換的虛擬地址合法范圍,只能在physmap_base和physmap_end或者gVirtBase和static_memory_end之間。接下來就是要循環遍歷l3頁表,l0頁表基地址保存在kernel_pmap->tte,這個頁表保存的是內核使用的頁表地址,在內核啟動時進行初始化。然后找到虛擬地址對應的l3 table地址, 循環遍歷它的每個頁表項,在這之前還需要將虛擬地址轉換為物理地址,然后找到物理地址對應的pv_head_table表項,將其上鎖。然后開始提取l3頁表項的原始權限,請看如下代碼邏輯:
LDR X8, [X20] ; 獲得pte的內容
TBZ W8, #1, loc_FFFFFFF007B598C4 ;判斷頁表有效位
TBNZ X8, #0x34, loc_FFFFFFF007B598FC ; '4' ; ARM_PTE_HINT_MASK
LSR X9, X8, #4
AND X9, X9, #0xC ; (*pte >> 4) & 0xc; 提取ap權限
LSR X10, X8, #0x35 ; '5' ; *pte >> 53,提取xn和pxn權限
BFXIL X9, X8, #0x35, #1 ; '5'
AND X10, X10, #2 ; (*pte >> 53) & 2
ORR X9, X9, X10
CMP X9, X26 ; 與函數的第三個參數expected_perm比較
B. NE loc_FFFFFFF007B59938
通過上面的提取后獲得一個4bit的權限值,高2位代表ap權限,最低位代表pxn權限,第2bit代表xn權限。
所以可以推斷出ppl新的權限模型為4個bit:
new_perm
X XXX
/ / \ \
/ / \ \
54 53 7 6
+-------------------------------------------------------
| |xn|pxn| ... |ap| | |
--------------------------------------------------------
El1和EL1_Guard level對應的權限關系可以參考m1n1的dock:
HW: SPRR and GXF · AsahiLinux/docs Wiki · GitHub

通過搜索代碼,發現有以下幾個函數調用了_pmap_set_range_xprr_perm:
_pmap_mark_page_as_kernel_page:
MOV W2, #1
MOV W3, #3
B _pmap_set_range_xprr_perm
_pmap_mark_page_as_ppl_page_internal:
MOV W2, #3
MOV W3, #1
BL _pmap_set_range_xprr_perm
可以看到將普通的內核權限轉化為ppl權限,是將3替換為1。反過來則是1替換為3。我們可以推測3為RW讀寫權限,1則為R只讀權限。
_pmap_static_allocations_done:
MOV W2, #3
MOV W3, #0xB
BL _pmap_set_range_xprr_perm
將_bootstrap_pagetables從0x3設置為0xb,_bootstrap_pagetables是內核啟動時用到的臨時頁表。
MOV W2, #3
MOV W3, #1
BL _pmap_set_range_xprr_perm ;
將_BootArgs從0x3設置為0x1,_BootArgs是iboot加載內核時傳給內核的參數。
MOV W2, #0x3
MOV W3, #1
BL _pmap_set_range_xprr_perm ;
將_segPPLDATAB從0x3設置為0x1,_segPPLDATAB為ppl的data段。
MOV W2, #0xA
MOV W3, #0x8
BL _pmap_set_range_xprr_perm ;
將_segPPLTEXTB從0xA設置為0x8,_segPPLTEXTB為ppl的text段。
MOV W2, #0xB
MOV W3, #0xB
BL _pmap_set_range_xprr_perm ;
將_segPPLDATACONSTB從0xB設置為0xB,_segPPLDATACONSTB為ppl的dataconst段。
MOV W2, #0x1
MOV W3, #0xB
BL _pmap_set_range_xprr_perm ;
將_pmap_stacks_start_pa從0x1設置為0xB,_pmap_stacks_start_pa為ppl使用的stack區域。
_pmap_ledger_alloc_internal:
MOV W2, #1
MOV W3, #3
BL _pmap_set_range_xprr_perm ;
_pmap_load_image4_trust_cache_internal:
MOV W2, #0xB
MOV W3, #1
BL _pmap_set_range_xprr_perm
MOV W2, #1
MOV W3, #0xB
BL _pmap_set_range_xprr_perm
### _pmap_release_ppl_pages_to_kernel_internal
它與_pmap_mark_page_as_ppl_page_internal是一個相反的操作,讀者朋友可以自行閱讀相關代碼。
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1768/
暫無評論