作者: 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_MONITORPP_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服務。

_GuardedExceptionVectorsBaseEL1_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_basephysmap_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是一個相反的操作,讀者朋友可以自行閱讀相關代碼。


Paper 本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1768/