作者:wzt
原文鏈接:https://mp.weixin.qq.com/s/TEUo6G1lWeCRujf6uH8hrQ

1 簡介

Fuchsia是google開發的全新微內核操作系統,用來替換android。本文根據fuchsia最新的官方代碼,來分析它所提供的一些安全功能。

2 安全功能

2.1 kaslr

現在的內核地址隨機化不止包含內核代碼段地址隨機化, 還包括了內核自身頁表、內核的堆等等。

2.1.1 內核代碼段地址隨機化

zircon內核并沒有支持內核地址隨機化, 以aarch64為例:

zircon/kernel/arch/arm64/start.S:

  adr_global  tmp, kernel_relocated_base

ldr   kernel_vaddr, [tmp]

Kernel_relocated_base符號保存著內核實際的加載地址,定義在:

zircon/kernel/arch/arm64/mmu.cc:

\#if DISABLE_KASLR

uint64_t kernel_relocated_base = KERNEL_BASE;

\#else

uint64_t kernel_relocated_base = 0xffffffff10000000;

\#endif

可以看到,當前是一個固定的值,注釋也強調了未來會使用隨機化。

// Static relocated base to prepare for KASLR. Used at early boot and by gdb

// script to know the target relocated address.

// TODO(fxbug.dev/24762): Choose it randomly.

X86架構同樣沒有實現:

zircon/kernel/arch/x86/mmu.cc

// Static relocated base to prepare for KASLR. Used at early boot and by gdb

// script to know the target relocated address.

// TODO(thgarnie): Move to a dynamically generated base address

\#if DISABLE_KASLR

uint64_t kernel_relocated_base = KERNEL_BASE - KERNEL_LOAD_OFFSET;

\#else

uint64_t kernel_relocated_base = 0xffffffff00000000;

\#endif
2.1.2 內核頁表地址隨機化

在商用os系統里了,windows nt內核首先將內核頁表做了地址隨機化處理,linux和xnu都沒有實現。

zircon內核也同樣沒有實現。

zircon/kernel/arch/arm64/mmu.cc

// The main translation table for the kernel. Globally declared because it's reached

// from assembly.

pte_t arm64_kernel_translation_table[MMU_KERNEL_PAGE_TABLE_ETRIES_TOP] __ALIGNED(

MMU_KERNEL_PAGE_TABLE_ENTRIES_TOP * 8);

2.2 aslr

Fuchsia的stack、代碼段(包含pie)等等都是通過zx_vmar_map函數來建立的,這是一個系統調用, fuchsia的系統調用似乎是自動生成的,筆者對這塊的邏輯尚未熟悉,不過在docs文檔里有說明是隨機化產生的地址。

**docs/reference/syscalls/vmar_map.md:**

*vmar_offset* must be 0 if *options* does not have **ZX_VM_SPECIFIC** or

**ZX_VM_SPECIFIC_OVERWRITE** set.  If neither of those are set, then

the mapping will be assigned an offset at random by the kernel (with an

allocator determined by policy set on the target VMAR).

2.3 code sign

fuchsia系統沒有提供app證書簽名和代碼完整性校驗的功能。

2.4 系統調用過濾

fuchsia系統沒有提供系統調用審計和過濾的功能。

zircon/kernel/arch/arm64/exceptions.S:

LOCAL_FUNCTION_LABEL(arm64_syscall_dispatcher)

  start_isr_func_cfi

  cmp  x16, #ZX_SYS_COUNT

  bhs  .Lunknown_syscall

  csel x16, xzr, x16, hs

  csdb

  adr  x12, .Lsyscall_table

  add  x12, x12, x16, lsl #4

  br  x12

SPECULATION_POSTFENCE

在判斷系統調用號是否超出范圍之后, 直接調用了syscall_table對應的函數指針。

沒有像linux提供了audit系統調用審計和secomp系統調用過濾的功能。

X86架構同樣如此:

zircon/kernel/arch/x86/syscall.S:

.balign 16

FUNCTION_LABEL(x86_syscall)

  // swap to the kernel GS register

  swapgs

  // save the user stack pointer

  mov   %rsp, %gs:PERCPU_SAVED_USER_SP_OFFSET

  // load the kernel stack pointer

  mov   %gs:PERCPU_KERNEL_SP_OFFSET, %rsp

  .cfi_def_cfa %rsp, 0

  push_value %gs:PERCPU_SAVED_USER_SP_OFFSET  // User stack

  push_value %r11  // RFLAGS

  push_value %rcx  // RIP

  push_value %r15

  push_value %r14

  push_value %r13

  push_value %r12

  push_value $0   // R11 was trashed by the syscall instruction.

  push_value %r10

  push_value %r9

  push_value %r8

  push_value %rbp

  push_value %rdi

  push_value %rsi

  push_value %rdx

  push_value $0   // RCX was trashed by the syscall instruction.

  push_value %rbx

  push_value %rax

  cmp   $ZX_SYS_COUNT, %rax

  jae   .Lunknown_syscall

  leaq   .Lcall_wrapper_table(%rip), %r11

  movq   (%r11,%rax,8), %r11

  lfence

jmp   *%r11

2.5 NULL Page Protection

fuchsia在提供的mmap接口中,沒有禁止映射內存0的限制,而這個功能在linux和NT內核中都做了限制。

zircon/third_party/ulib/musl/src/mman/mmap.c

mmap的主體函數中沒有對addr地址做限制。

2.6 mmap/mprotect w^x保護

同上, mmap/mprtect接口中沒有對可寫、可執行權限做限制, linux、xnu、nt都實現了此保護功能。

2.7 printf %K內核地址保護

Printf未實現%K內核地址保護功能,利用%p可能將內核地址泄露出來。

zircon/third_party/ulib/musl/src/stdio/vfprintf.c

2.8 Ref counter保護

Fuchsia未實現類似linux的引用計數溢出保護。

zircon/kernel/lib/counters/counters.cc

2.9 kernel/module rwx保護

zircon內核在啟動之出對內核代碼段的屬性設置為rwx,在后續的vm_init中,將內核的代碼和數據屬性進行了正確設置:

zircon/kernel/vm/vm.cc:

namespace {

const ktl::array _kernel_regions = {

  kernel_region{

?    .name = "kernel_code",

?    .base = (vaddr_t)__code_start,

?    .size = ROUNDUP((uintptr_t)__code_end - (uintptr_t)__code_start, PAGE_SIZE),

?    .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_EXECUTE,

  },

  kernel_region{

?    .name = "kernel_rodata",

?    .base = (vaddr_t)__rodata_start,

?    .size = ROUNDUP((uintptr_t)__rodata_end - (uintptr_t)__rodata_start, PAGE_SIZE),

?    .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ,

  },

  kernel_region{

?    .name = "kernel_data",

?    .base = (vaddr_t)__data_start,

?    .size = ROUNDUP((uintptr_t)__data_end - (uintptr_t)__data_start, PAGE_SIZE),

?    .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,

  },

  kernel_region{

?    .name = "kernel_bss",

?    .base = (vaddr_t)__bss_start,

?    .size = ROUNDUP((uintptr_t)_end - (uintptr_t)__bss_start, PAGE_SIZE),

?    .arch_mmu_flags = ARCH_MMU_FLAG_PERM_READ | ARCH_MMU_FLAG_PERM_WRITE,

  },

};

}

2.10 kernel stack canary

fuchsia使用clang編譯器,默認開啟了fstack-protector參數。

在啟動階段設置thread pointer地址時就開始引入了一個隨機化的stack canary值。

zircon/kernel/arch/arm64/start.S

bl    choose_stack_guard

zircon/kernel/top/debug.cc

__NO_SAFESTACK uintptr_t choose_stack_guard(void) {

 uintptr_t guard;

 if (hw_rng_get_entropy(&guard, sizeof(guard)) != sizeof(guard)) {

  // We can't get a random value, so use a randomish value.

  guard = 0xdeadbeef00ff00ffUL ^ (uintptr_t)&guard;

 }

 return guard;

}

以后每個線程在創建的時候,都會使用上述初始化選定的canary值。

zircon/kernel/arch/arm64/thread.cc

void arch_thread_initialize(Thread* t, vaddr_t entry_point) {

 // compiler ABI (TPIDR_EL1 + ZX_TLS_STACK_GUARD_OFFSET).

 t->arch().stack_guard = Thread::Current::Get()->arch().stack_guard;



 // set the stack pointer

 t->arch().sp = (vaddr_t)frame;

\#if __has_feature(safe_stack)

 DEBUG_ASSERT(IS_ALIGNED(t->stack_.unsafe_top(), 16));

 t->arch().unsafe_sp = t->stack_.unsafe_top();

\#endif

\#if __has_feature(shadow_call_stack)

 // The shadow call stack grows up.

 t->arch().shadow_call_sp = reinterpret_cast<uintptr_t*>(t->stack().shadow_call_base());

\#endif

}

所以每個線程使用的canary值都是同一個, 較linux每個線程使用不同的值有所弱化。

2.11 shadow stack

fuchsia使用的是clang編譯器,默認使用了-fsanitize=shadow-call-stack,llvm的官方文檔指出僅在aarch64上實現了shadow stack的功能,x86_64上因為一些安全原因將這個功能移除主線代碼了。

Aarch64上,fuchsia的abi約定x18寄存器保存的是shadow stack的地址,由于筆者尚未編譯完zircon內核二進制,以下示例來自llvm官方文檔。

int foo() {

 return bar() + 1;

}

不開啟shadow stack:

stp   x29, x30, [sp, #-16]!

mov   x29, sp

bl    bar

add   w0, w0, #1

ldp   x29, x30, [sp], #16

Ret

開啟后:

str   x30, [x18], #8

stp   x29, x30, [sp, #-16]!

mov   x29, sp

bl    bar

add   w0, w0, #1

ldp   x29, x30, [sp], #16

ldr   x30, [x18, #-8]!

ret

2.12 safe stack

Safe stack與shadow stack功能非常相近,使用-fsanitize=safe-stack參數編譯。Shadow stack僅僅保存函數的返回地址,而safe stack除了保存返回地址外,一些寄存器值和局部變量的值也會保存。

在shadow stack中, sp保存的是shadow stack的地址,x18寄存器保存的是正常stack的地址。而在safe stack中,sp保存的是safe stack的地址,而unsafe stack的地址是保存在fs或gs寄存器的某個固定偏移中。

X86_64:  %gs:ZX_TLS_UNSAFE_SP_OFFSET

Aarch64:  __builtin_thread_pointer()或者TPIDR_EL0/1

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