1

vDSO 代表虚拟共享对象(Dynamic Shared Object)的意思,Zircon vDSO 是 Zircon 内核访问系统调用的唯一方法。它不是从文件系统中的ELF文件加载的,而是由内核直接提供 vDSO 镜像。

它是一个用户态运行的代码,被封装成 shared librarylibzircon.so elf文件。这个elf .so 文件装载不是放在文件系统中, 而是由内核提供。它被整合在内核image中。

这个vdso 有2个页的大小, 放在内核中(准确说是在内核IMAGE中的一个地方)。内核启动时,会通过计算得到它所在的物理页,然后在创建用户进程时,program loader (userboot 第一个进程会用特别的方式)会把这二个物理页mapping到用户进程的虚地址进程空间,且位置是radom的和只读。进程内的程序无法修改它。

为了进一步增加安全, 所有用户进程内的程序不能直接调用(ARM64 svc #NUM)的 syscall 进入内核,一定要调用VDSO的wrappersyscall, 也就是说, syscall 真正的调用,一定要由vdso所在的代码来调用。其它位置上调用是无效的,这样防止用户程序恶意去攻击syscall。

所有的vdso calls 都用了 GCC attribute中的leaf function (__LEAF_FN) , 就是它们的函数不能向前调其它的函数,也不能作返回。这样来说,更安全。

如果做到限制syscall 指令在vdso的代码中才有效呢?

User space Vdso 中的syscall 代码和 kernel space syscall exception的代码是同abigen 产生的。

静态编译时:

在abigen生产代码时,作一个总的wrapper函数,为每一个syscall函数最后一行加一个label , 记录用户态的vdso 发生syscall 的svc 指令的位置,然后也知道vdso_base_address, 这样, 我可以知道 vdso_syscall_xxx offest:

static_Vdso_syscall_xxxoffset = static_Vdso_syscall_xxx_label – static_vdso_base_address.

这里, 我的 vdso_base_address 是一个事先假设的地址,在运行时,它被mapping 到用户进程会被随机改动位置。但vdso 是一个rodso read only的代码片段, 是固定的,不会被改动。所以无论base address 如何改,这个 offset 不会改。

编译后,这些vdso syscall的offset被生成一个头文件 build-arm64/kernel/lib/vdso/vdso-code.h

在 runtime 时:

内核的process 内核对象会先记录这个process的vdso随机mapping的base address(这个在建新进程时会完成)。

然后 当发生 vdso svcsyscall 进入内核时, 我们保存发生时 pc 指令值, 也就是 svc指令的下一条。这个位置正好与 vdso 中 wapper 函数的label 位置一样。

所以我们在进入内核后,马上先做一个检查(abigen生产的)

runtimesyscall offset = 保存的pc -current_process->vdso_base_address

如果runtime offset 与 compilation time的vdso-code.h中的值一样,就是好的syscall,不然是其它地方的svc 发生的,就是恶意的。

代码:

Gen/global/include/zircon/syscalls-arm64.S
This is a GENERATED file, see//zircon/system/host/abigen

m_syscall zx_channel_create 21 3 1

zircon/system/ulib/zircon/syscalls-arm64.S

.macro m_syscall name, num, nargs, public
syscall_entry_begin \name
zircon_syscall\num, \name, \name
ret
syscall_entry_end \name \public
.endm

zircon/system/ulib/zircon/syscall-entry.h

.macrosyscall_entry_begin name
.globlSYSCALL_\name
.hiddenSYSCALL_\name
.typeSYSCALL_\name,STT_FUNC
SYSCALL_\name:
.cfi_startproc
.endm

.macrosyscall_entry_end name public=1
.cfi_endproc
.sizeSYSCALL_\name, . – SYSCALL_\name

zircon/system/ulib/zircon/zircon-syscall-arm64.S

.macro zircon_syscall num, name, caller
mov x16, #\num
svc #0x0
// Thissymbol at the return address identifies this as an approved call site.
.hidden CODE_SYSRET_\name\()_VIA_\caller
CODE_SYSRET_\name\()_VIA_\caller\():
.endm

当vsdo 用svc 指令后,这时CPU exception进入内核,到 expections.S 中的 sync_exception 宏(不同ELx, sync_exception的参数不一样)。然后这个 sync_exception 宏中先做一些现场保存的工作, 然后jump到 arm64_syscall_dispatcher 宏。

进入arm64_syscall_dispatcher宏后, 先做一些syscall number检查,然后syscall number 跳到 call_wrapper_table 函数表中相应index项的函数中去(call_wrapper_table 像一个一维的函数指针的数组,syscall number 作index,jump到相应的wrapper syscall function 函数中去)。

这个wrapper syscallfunction 会调用 do_syscall, 让VDso::ValidSyscallPC 函数作参数,去检查vdso中的调用syscall 指令的那个函数 所在的地址在vdso start 的地址范围内。注意 这个vdso 函数内有一个label作地址。

global/include/zircon/syscall-kernel-branches.S

This is aGENERATED file, see //zircon/system/host/abigen
start_syscall_dispatch

syscall_dispatch 3 channel_create

zircon/kernel/arch/arm64/expections.S

// Adds alabel for making the syscall and adds it to the jump table.
475 .macro syscall_dispatchnargs, syscall
476 .pushsection .text.syscall-dispatch,”ax”,%progbits
477 LOCAL_FUNCTION(.Lcall_\syscall\())
478 pre_args \nargs
479 bl wrapper_\syscall
480 post_args \nargs
481 END_FUNCTION(.Lcall_\syscall\())
482 .popsection
483 .pushsection .rodata.syscall-table,”a”,%progbits
484 .quad .Lcall_\syscall
485 .popsection
486 .endm

// Addsthe label for the jump table.
489 .macro start_syscall_dispatch
490 .pushsection .rodata.syscall-table,”a”,%progbits
491 // align on 4096 byte boundary to save an instruction on table lookup
492 .balign 4096
493 call_wrapper_table:
494 .popsection
495 .endm

gen/global/include/zircon/syscall-kernel-wrappers.inc:
This is aGENERATED file, see //zircon/system/host/abigen.
syscall_resultwrapper_channel_create(uint32_t options, zx_handle_t* out0, zx_handle_t* out1, uint64_t pc) {

145 return do_syscall(ZX_SYS_channel_create, pc, &VDso::ValidSyscallPC::channel_create, [&](ProcessDispatcher* current_process) -> uint64_t {
146 user_out_handle out_handle_out0;
147 user_out_handle out_handle_out1;
148 auto result = sys_channel_create(options, &out_handle_out0,&out_handle_out1);
149 if (out_handle_out0.begin_copyout(current_process,make_user_out_ptr(out0)))
150 return ZX_ERR_INVALID_ARGS;
151 if (out_handle_out1.begin_copyout(current_process,make_user_out_ptr(out1)))
152 return ZX_ERR_INVALID_ARGS;
153 out_handle_out0.finish_copyout(current_process) ;
154 out_handle_out1.finish_copyout(current_process) ;
155 return result;
156 });
157 }

kernel/lib/vdso/vdso-valid-sysret.h:
generatedby scripts/gen-vdso-valid-sysret.sh
structVDso::ValidSyscallPC {

static bool channel_create(uintptr_t offset){
213 switch (offset) {
214 case VDSO_CODE_SYSRET_zx_channel_create_VIA_zx_channel_create -VDSO_CODE_START:
215 return true;
216 }
217 return false;
218 }
}

// kernel/syscalls/syscalls.cpp
template
inlinesyscall_result do_syscall(uint64_t syscall_num, uint64_t pc,
bool(*valid_pc)(uintptr_t), T make_call) {
ProcessDispatcher* current_process =ProcessDispatcher::GetCurrent();

//建新进程时,vdso code 被随机分配 开始地址
const uintptr_t vdso_code_address =current_process->vdso_code_address();
uint64_t ret;
if (unlikely(!valid_pc(pc -vdso_code_address))) { //要先验证发生syscall所在的地址与vdsocode是不是一样
ret = sys_invalid_syscall(syscall_num,pc, vdso_code_address);
} else {
ret =make_call(current_process); 验证成功后,调用真正的syscall对应处理函数
}

// The assembler caller will re-disableinterrupts at the appropriate time.
return {ret,thread_is_signaled(get_current_thread())};
}


Credit:本文作者:

赵杭君

Vivo 公司确认加入 Google Fuchsia OS 生态,正在开发相关产品

Previous article

已经加入 Google Fuchsia OS 生态的产业巨头大盘点

Next article

You may also like

1 Comment

  1. 前面通俗易懂,后面到代码那里的排版真的看不下去了…

Leave a reply

您的电子邮箱地址不会被公开。 必填项已用 * 标注

More in 开发