作者: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
本文由 Seebug Paper 發布,如需轉載請注明來源。本文地址:http://www.bjnorthway.com/1592/
暫無評論