Jenny: Securing Syscalls for PKU-based Memory Isolation Systems
论文动机
- 保护系统调用很重要,但是方法需要“升级”
- 一般方法在内核中运行系统调用过滤器
- 不安全
- 限于进程层面
- 更细粒度的进程内沙箱逐渐流行
- 基于PKU的进程内沙箱技术
- ERIM、Hodor有缺陷
- Donky只拦截内存相关的syscall
- SealPK没有考虑系统调用的安全
- 嵌套域的处理、signal信号的安全处理等挑战
论文贡献
-
发现了新的针对基于PKU的隔离系统的系统调用攻击
-
设计了保护基于PKU的隔离域的系统调用过滤规则
-
进行了各种系统调用介入(interposition)技术对PKU的适用性比较研究,并提出了一种为PKU定制的新方案
-
设计了Jenny——第一个全面的基于PKU的隔离系统,支持安全的(同线程)用户空间系统调用过滤,安全的(异步)信号处理,以及x86-64下的安全多域PKU call gate
-
在不同的系统调用介入技术和过滤规则下对Jenny进行了原型实现和评估,并且代码开源
背景知识
- 系统调用过滤
约束进程,阻止其对某些系统调用和内核资源的访问;可限制暴露潜在的内核漏洞,减小攻击面
-
ptrace:允许拦截被跟踪进程(tracee)系统调用的入/出口
- seccomp-bpf:seccomp为内核机制,能将进程可用的系统调用限制到最低限度。seccomp-bpf允许用户使用可配置的策略过滤系统调用,可以根据系统调用编号及其参数进行过滤
- SECCOMP_RET_TRACE:通知基于ptrace的跟踪进程,”seccomp-ptrace”
- SECCOMP_RET_TRAP:向调用者发送signal,”seccomp-trap”
- SECCOMP_RET_USER_NOTIF:让用户空间程序做决策,”seccomp-user”
- Syscall User Dispatch:新的系统调用仿真机制
- 某个内存区域内的系统调用会正常执行
- 区域外部的系统调用交给用户空间的信号handler处理
- PKU技术
- Protection Keys for User-Space(a.k.a. MPK)
-
32-bit的PKRU寄存器(Access/Write Disable)
-
WRPKRU/RDPKRU、XRSTOR
- 沙箱技术
-
一种安全机制,为执行中的程序提供的隔离环境,通常会严格控制其中的程序所能访问的资源
-
可用于限制潜在的恶意代码或有漏洞的代码影响系统的其余部分
-
存在不同形式的沙箱(例如,虚拟机、容器、进程等),但这篇文章考虑基于PKU的进程内沙箱
-
与其他进程内隔离技术类似,基于PKU的沙箱也必须过滤系统调用接口,避免逃逸
- PKU沙箱技术
基于Intel MPK的PKU沙箱的著名例子有ERIM、Hodor和Donky
-
ERIM:将MPK与二进制检查相结合,在高频率域切换时也具有较低的运行时开销
-
Hodor:检查可执行区域(加载时、权限变化时)、寻找WRPKRU指令、硬件断点指向指令序列以监视
-
Donky:基于动态内存保护域的强进程内隔离的高效软硬件协同设计方法,包括一个安全的软件框架和一个非侵入式的硬件扩展
本文工作中,作者主要关注Donky,Jenny构建在其软件框架之上。Donky是一个面向RISC-V和x86架构的PKU系统。针对RISC-V,它提出ISA修改,将违反保护密钥的行为转发给进程内监视器。当进入和退出监视器时,通过翻转策略寄存器中的位,Donky确保只有监视器可以更改寄存器。因此,不需要二进制扫描或W⊕X。Donky软件也可以与英特尔MPK一起使用,但只能以不安全的方式使用,因为它不能保护PKRU。
为什么需要PKU-aware的系统调用过滤
- 针对PKU沙箱有各种各样的攻击:
- 一些系统调用允许绕过PKRU权限访问任意内存(例如,process_vm_writev、ptrace)。类似地,/proc/self/mem处的procfs虚拟文件允许通过文件操作被不受限制地访问
- 只读内存可以改变(例如文件背景页),攻击者可以通过写入文件来生成不安全的WRPKRU指令
- 不可信域可以安装seccomp过滤器来操纵PKU相关系统调用,如pkey_mprotect等
- 不可信域可以注册信号处理程序并使用sigreturn系统调用将PKRU提升到任意权限
- 本文面临的挑战
- 如何获取PKU安全相关的系统调用过滤规则?
- 分析系统调用接口
- 定义PKU保护的资源,推导过滤规则
- 对于PKU沙盒,现有的系统调用介入技术有何限制,如何克服?
- 分析不同的系统调用介入技术
- 设计了“pku-user-delefate”
- 对于PKU沙盒,能否实现有效而全面的系统调用过滤?
- 灵活定义过滤规则
- 感知不同的PKU域
- 过滤规则的嵌套
- 如何安全地结合沙箱(PKU感知)和系统调用过滤(能处理信号)?
- 独立的栈保存信号帧
- 虚拟化子域处理
- 安全的信号处理handler
- 如何获取PKU安全相关的系统调用过滤规则?
- 威胁模型
-
非特权用户进程可以在沙盒中运行不可信的代码(例如插件),并对其进行防护
-
对沙箱代码没有任何假设——它可能包含可利用的漏洞,也可能是完全恶意的,能执行攻击者提供的任意代码
-
可信计算基由沙盒代码中的可信组件(PKU监视器、启动代码和链接器)、内核、一个内核模块和硬件组成,假设这些部分的实现是正确的,并且没有可利用的漏洞
-
硬件缺陷、旁路和故障攻击都不在讨论范围之内。作者使用Donky框架作为基础,用星号“*”标记任何依赖于Donky提出的硬件更改的过滤器和机制
- 新的基于syscall的攻击
并非所有系统调用都会在访问用户空间内存之前检查PKRU寄存器。作者通过检查系统调用文档,作者确定了一些先前未知的、不同资源类别(即内存、进程、文件)的可利用系统调用:
- 内存相关
- madvise:无视保护键地清理页面
- brk、sbrk:清理页面并移除保护键
- userfaultfd:向不在内存的被保护页面任意写
- 进程相关
- (v)fork、clone
- exec-like
- arch_prctl、set_thread_area
- personality(READ_IMPLIES_EXEC)
- 文件相关
- process_vm_writev
- core dump:获取敏感的内存、寄存器状态等信息
- 系统调用过滤规则
- base-mpk
额外定义针对mmap(2), (pkey_)mprotect, mremap, munmap, madvise的系统调用过滤规则
磁盘映射安全:拦截mmap、mprotect,禁止共享的可执行映射
确保可执行内存不是由可写文件支持的——如果用户可以修改文件就禁止映射
core dump和procfs——使用内核特性prctl(PR_SET_DUMPABLE, SUID_DUMP_DISABLE),禁用core dump和对procfs的访问
PKRU寄存器:扫描不安全的指令;对内存映射等实施W⊕X策略;拦截pkey_alloc与pkey_free以保护pkey
过滤fork、clone、signal、sigaltstack等相关系统调用 ,阻断能修改整个进程行为的系统调用
- localstorage
domain可能访问敏感的资源,如数据库、私钥等,需对其他domain屏蔽
过滤基于路径和基于文件描述符的系统调用
为每个domain创建本地存储目录,基于路径的系统调用只能访问域自己的本地存储,会透明地修改系统调用中的路径参数,如/tmp/file会被转换为/tmp/localstorage[PID]/[domain id]/tmp/file
domain只能使用它们可以访问的文件描述符
- base-donky*
过滤器与base-mpk相同,但不实施W⊕X策略
只保护monitor代码和file-backing
无二进制扫描,需要应用Donky提出的硬件修改来保护PKRU寄存器
系统调用过滤机制
- 现有机制对比
无法感知PKU域、依赖不安全的信号处理、过滤器不够灵活、速度慢、x86-64不可用
- pku-user-delegate
-
内核模块
- 选择性地拦截系统调用
- thread_info结构体设置bit位
- 拦截监视器注册的系统调用
- 非监视器域系统调用
- 用户空间监视器进行过滤
- 减小内核攻击面
- 过滤器代码更灵活
- 过滤规则易拓展
Jenny-针对PKU的安全高效系统调用过滤
第一个全面的基于PKU的隔离系统,支持安全高效的系统调用过滤、信号处理和多域间的PKU call gate;基于PKU框架Donky并拓展,为注册、调用系统调用过滤器和信号处理handler提供了必要的逻辑。过滤器是PKU域感知的,允许跨域的分层系统调用过滤。Jenny为用户定义的系统调用过滤器提供了一个通用的编程接口,支持不同的过滤机制;为了适用原生x86-64 CPU,实现了W⊕X策略、二进制扫描和安全call gate。最后,进行安全性论证并设计了PKU安全信号处理方案。
- 设计
- 同线程嵌套过滤
- 过滤器设计
系统调用参数在过滤过程中可能改变
-
拷贝系统调用中的敏感指针参数——防止TOCTOU攻击
-
PKRU寄存器检查
-
数据结构上锁
- 每个数据结构必须只被一个域(例如根域)锁定和访问
- 为了避免通过递归产生死锁,过滤器代码本身不能执行任何显式的域切换
- 多域call gate
call gate是进入PKU域的安全入口点
- 传统的call gate
- 防止攻击者恶意重用WRPKRU指令
- double-check %rax寄存器
- 只支持静态的$PKRUVALUE
- 通用的多域call gate
- PKRU值从线程本地存储获取
- 信号处理
-
安全的信号API:监视器仅允许一个信号由一个域处理。此外,父域可以覆盖由其子域注册的信号处理handler,从而对其虚拟化子域。父级可以决定是否将信号重定向到子域
-
安全的信号stack:为每个线程分配独立的信号堆栈,并用pkey保护,两者同时注册到内核(拓展了sigaltstack系统调用)
-
安全的信号handler:消除竞态条件
- 暂时阻塞所有的信号
- 防止信号干扰当前的监视器执行,将信号处理推迟到监视器退出点
- 避免信息泄露给信号handler,只将signal帧放在受保护的signal栈上,不提供给域的信号handler
- 安全论证
- 系统调用介入技术
- 无法抵御execve攻击——拦截execve系统调用
- 可通过ioctl重新配置——初始化之后再拦截ioctl
- 基本过滤器
- 不能保证阻止所有的系统调用攻击——默认拦截新的系统调用
- 使用strace对系统调用分类,再手工分析
- Jenny设计:确保域都会进行系统调用过滤
- 应用过滤规则:对系统调用参数进行“清理”
评估
- Micro-benchmarking
- Application Benchmarks
- Case study:nginx webserver
讨论
- 兼容性问题
- 系统调用限制
- localstorage限制
- 不安全的WRPKRU (以及 XRSTOR )指令
- 内核接口
- Linux内核的系统调用接口数理与复杂程度都在增加
- 建议只允许必要的系统调用,过滤未知的系统调用
- pkey数量
- Intel MPK只支持16个key
- 保护过滤的系统调用参数的策略,造成pkey大量使用,即sysargs key
- 解决方案是对sysargs key进行请求式分配
结论
-
解决了基于PKU内存隔离系统的各种系统调用过滤挑战,从而实现了第一个具有全面系统调用支持的PKU内存隔离系统Jenny
-
发现了新的与PKU相关的系统调用攻击,比较了各种系统调用拦截机制,并设计了一种更快的符合PKU系统需求的机制
-
设计了全面高效的过滤规则以保护PKU沙箱
-
Jenny在同一个线程上提供过滤,支持嵌套的系统调用过滤和信号处理
-
证明了Jenny的系统调用过滤既实用又安全,并且对nginx的性能影响只有5%
附:
- 信号处理过程
- “内核空间的PKRU”
Protection Keys for Supervisor(PKS):在内核中修改page protection的bit的话,操作会比较耗时,因为这里牵涉到了TLB invalidation等会影响系统性能的动作。因此频繁使用这个机制的话会损害kernel的性能。通常来说禁止kernel访问内存是用来预防kernel bug导致破坏的,而这种bug其实并不常见,因此多数人并不喜欢为了这个目的而损失性能。PKS是针对未来的Intel CPU所准备的功能。PKS会对kernel地址空间中的每个page都跟一个4-bit的protection key关联起来,这样每个page都是属于16个内存区域中的一个。每个内存区域都可以被独立地配置为禁止kernel的写操作,或者彻底禁止kernel的所有访问。修改这些限制策略会比直接修改这些page的protection bit设置要更加高效。
[PKS: Add Protection Keys Supervisor (PKS) support LWN.net]
- 特殊的系统调用绕过PKRU说明
madvise
绕过PKRU限制:
flag
MADV_DONTNEED:在未来一段时间不会访问。成功执行MADV_DONTNEED操作后,将更改指定区域中的内存访问的语义:对该范围内页面的后续访问将成功,但是将导致从底层映射文件(共享文件映射,共享匿名映射和基于shmem的技术,例如System V共享内存段)的最新内容中重新填充内存或按需按零填充页面(私有匿名映射)。
MADV_FREE:该应用程序不再需要由addr和len指定的范围内的页面。内核可以释放这些页面,但是释放可以延迟到出现内存压力为止。对于已标记为将释放但尚未释放的每个页面,如果调用者写入该页面,则释放操作将被取消。成功执行MADV_FREE操作后,内核释放页面时,所有过时的数据(即脏的,未写入的页面)都将丢失。但是,随后对该范围内的页面的写操作将成功,然后内核无法释放那些脏页,从而使调用者始终只能看到已写的数据。如果没有后续写入,则内核可以随时释放页面。释放范围内的页面后,调用者将在随后的页面引用中看到按需填充零页面。MADV_FREE操作只能应用于私有匿名页面。
MADV_WIPEONFORK:在fork之后,在此范围内为子进程提供零填充内存。这对于服务器fork很有用,以确保不会将敏感的进程数据(例如PRNG种子,加密机密等)传递给子进程。该操作只能应用于私有匿名页面。
攻击者利用这个syscall去操控内核清理特定的内存页面,而这种行为是不受MPK/PKU控制的(因为MPK/PKU是在用户空间发挥限制的手段)。
brk,sbrk
绕过PKRU限制:
brk,sbrk都是用来管理堆内存的。由于PKRU允许domain分配内存、保护私有内存,如果这些内存在堆上的话,恶意domain可以用brk,sbrk解除分配或者重新分配内存。
brk完成内存分配后,还是没有物理页与之对应的, 等到进程第一次读写这块内存的时候,发生缺页中断,内核才分配这块内存对应的物理页。这个过程是不涉及PKRU检查的。