Zircon重点介绍:vDSO(虚拟动态共享对象)

锆石? 这是什么


2016年8月,在没有Google的任何正式公告的情况下,发现了新操作系统的来源 紫红色。 该操作系统基于名为Zircon的微内核,而微内核又基于LK(小内核)


紫红色不是Linux

翻译笔记

我不知道 真正的焊工 我是Zircon的开发人员和/或专家。 删节部分的文本是部分翻译的汇编Zircon vDSO官方文档欣赏 Zircon第1部分:了解 @depletionmode的 最小过程创建一 ,其中添加了一些插科打 ((已删除掉扰流板)。 因此,一如既往地欢迎提出改进文章的建设性建议。


本文将讨论什么?


Zircon中的vDSO是访问系统调用(syscalls)唯一方法。


真的不可能从我们的代码中直接调用处理器指令SYSENTER / SYSCALL吗? 不,这些处理器指令不是系统ABI的一部分。 禁止用户代码直接遵循这些说明。


那些想了解有关此类架构步骤的更多详细信息的人,我邀请您加入Cat。



Zircon vDSO(虚拟动态共享对象)


首字母缩写词vDSO代表虚拟动态共享对象:


  • 动态共享对象是一个术语,用于引用ELF格式的共享库(.so文件)。
  • 该对象是虚拟的,因为它不会从文件系统上现有的单独文件中加载。 vDSO映像由内核直接提供。

内核支持


支持vDSO作为用户模式应用程序的唯一受控ABI的支持有两种方式:


  1. 投影虚拟内存对象( VMO,虚拟内存对象 )。

    zx_vmar_map处理vDSO的 VMO(并且在参数中请求ZX_VM_PERM_EXECUTE )时,内核要求偏移量和大小必须与正在执行的vDSO段严格一致。 这(包括)确保仅将vDSO投影到过程存储器中。 将vDSO首次成功投影到流程中之后,将无法再将其删除。 尝试将vDSO重新投影到进程内存中,尝试删除vDSO的投影VMO或具有错误偏移和/或大小的投影失败,并显示错误ZX_ERR_ACCESS_DENIED
    vDSO代码的偏移量和大小在编译阶段从ELF文件中提取,然后在内核代码中用于执行上述检查。 vDSO首次成功投影后,OS内核会记住目标进程的地址,以加快检查速度。


  2. 检查系统调用函数的返回地址。

    当用户模式代码调用内核时,低级系统调用号将在寄存器中传输。 低级系统调用是vDSO和Zircon内核之间的内部(专用)接口。 一些(大多数)直接对应于公共ABI的系统调用,而另一些则不对应。
    对于vDSO代码中的每个低级系统调用,在进行此调用的代码中都有一组固定的偏移量。 vDSO的源代码定义了标识每个此类位置的内部字符。 在编译时,将从vDSO符号表中检索这些位置,并用于生成内核代码,该内核代码确定每个低级系统调用的代码地址有效性的预测。 给定与vDSO代码段开头的偏移量,这些谓词使您可以快速检查调用代码的有效性。
    如果谓词确定不允许调用代码进行系统调用,则将引发综合异常,类似于调用代码试图执行不存在或特权的指令的情况。



创建新流程时的vDSO


要开始执行新创建的进程的第一个线程,请使用zx_process_start系统调用。 该系统调用的最后一个参数(请参见文档中的arg2)传递所创建进程的第一个线程的参数。 根据公认的协议,程序加载器将vDSO映射到新进程的地址空间(到系统选择的随机位置),并将带有arg2参数的映射的基地址传输到创建的进程的第一个线程。 该地址是ELF文件的标头地址,在该地址处可以找到进行系统调用所需的命名函数。


存储卡(布局)vDSO


vDSO是常规的EFL共享库,可以像其他任何库一样考虑。 但是对于vDSO,有意选择了整个ELF格式的一小部分。 这有几个优点:


  • 这样的ELF到过程的映射很简单,并且不包括完全支持ELF程序所需的任何复杂边界情况。
  • 使用vDSO不需要功能齐全的动态ELF绑定。 特别是,vDSO没有动态重定位。 投影ELF文件的PT_LOAD段是唯一需要执行的操作。
  • vDSO代码是无状态且可重入的。 它专用于处理器寄存器和堆栈。 这使其适合在各种环境中使用,并且具有最小的限制,符合强制性ABI操作系统。 它还简化了代码分析和验证的可靠性和安全性。

所有vDSO内存均由两个连续的段表示,每个段包含对齐的整页:


  1. 第一段是只读的,包括ELF标头和常量数据。
  2. 第二段是可执行的,包含vDSO代码。

整个vDSO映像仅由这两部分的页面组成。 显示vDSO内存仅需要从ELF标头提取的两个值:每个段中的页数。


操作系统启动时间常数数据


某些系统调用仅返回恒定的值(这些值必须在运行时请求,并且不能编译为用户模式代码)。 这些值要么在编译时在内核中固定,要么在启动时由内核确定(启动参数和硬件参数)。 例如: zx_system_get_version()zx_system_get_num_cpus()zx_ticks_per_second() 。 例如,最后一个函数的返回值受内核命令行参数的影响。


等一下,CPU的数量是常数吗?

有趣的是, zx_system_get_num_cpus()函数的描述还明确指出该操作系统不支持 交换处理器数量:


仅在引导时才能在系统运行期间更改此数字。

这至少间接地指示OS未定位为服务器。


由于这些值是恒定的,因此无需为对OS内核的实际系统调用付费。 相反,它们的实现是简单的C ++函数,该函数返回从vDSO常量段读取的数据。 编译期间捕获的值(例如系统的版本字符串)仅被编译到vDSO中。


对于引导时确定的值,内核应修改vDSO的内容。 这是通过在内核启动第一个用户进程(并将其传递给VMO描述符)之前使用形成VMO vDSO的早期可执行代码完成的。 在编译期间,将从ELF文件中提取vDSO映像的偏移量( vdso_constants ),然后将其嵌入到内核中。 并且在引导时,内核会在其自己的地址空间中临时显示跨越vdso_constants的页面,以使用正确的值(对于当前系统启动)预先初始化该结构。


为什么这一切令人头疼


最重要的原因之一是安全性。 也就是说,如果攻击者设法执行任意(shell)代码,则他将不得不使用vDSO函数来调用系统函数。 第一个障碍将是上述针对每个创建的进程的vDSO引导地址的随机化。 而且由于OS内核负责vDSO的VMO(虚拟内存对象),因此它可以选择将完全不同的vDSO映射到特定进程,从而避免了危险的(对于特定进程而言并不需要)系统调用。 例如:您可以防止驱动程序产生子进程或处理突出的MMIO区域。 这是减少攻击面的好工具。


注意:当前,正在积极开发对多个vDSO的支持。 已经有概念验证的实现和简单的测试,但是需要做更多的工作来提高实现的可靠性并确定可用的选项。 当前概念提供了仅导出完整vDSO系统调用接口的子集的vDSO映像选项。


那其他操作系统呢?

应当注意,类似的技术已经在其他操作系统中成功使用。 例如,在Windows上有一个ProcessSystemCallDisablePolicy


Win32k系统调用禁用限制以限制使用NTUser和GDI的能力

Source: https://habr.com/ru/post/zh-CN435482/


All Articles