gvisor
gvisor是google发布的安全容器。
gVisor 工作的核心,在于它为应用进程(用户容器),启动了一个名叫 Sentry 的进程。 而 Sentry 进程的主要职责,就是提供一个传统的操作系统内核的能力,即:运行用户程序,执行系统调用。所以说,Sentry 并不是使用 Go 语言重新实现了一个完整的 Linux 内核,而只是一个对应用进程“冒充”内核的系统组件。(这段是抄的)
作为安全容器,利用容器中的内核来隔离大部分内核攻击,确实是一个不错的想法,然而这个新内核属于定制内核,估计很多功能还不完善,据说用了ptrace和kvm两种方式实现,我这里来探索下kvm的实现方式。
kvm
kvm是linux的硬件虚拟化的的抽象模块,方便用户构建硬件虚拟化的虚拟机。最全的资料应该还是intel或amd各家的硬件开发手册,我之前有几篇相关的文章,所以就不啰嗦VT虚拟化了。
gviosr如何劫持容器中的系统调用
根据我的观察,gvisor对于VT技术(kvm)的使用核心逻辑图如下:
这里在信号里创建了个VMM(虚拟机管理器)。简单讲就是初始化客户机状态,和VMM状态,设置各种vm_exit标识,用来劫持客户机的各种硬件或软件行为。之后在kvm的设备句柄中 发送_KVM_RUN状态就可以运行客户机代码,也就是我们需要的新内核的代码。
kvm中设置系统调用表地址
MSR_LSTAR代表内核可以操作的系统寄存器,一般用来存放系统调用表。如上图,linux内核也是这样做的,存放了entry_SYSCALL_64。
gvisor利用kvm对虚拟机化中的系统表进行改写位于kernel——amd64.go(startGo)
func startGo(c *CPU) {
// Save per-cpu.
WriteGS(kernelAddr(c.kernelEntry))
//
// TODO(mpratt): Note that per the note above, this should be done
// before entering Go code. However for simplicity we leave it here for
// now, since the small critical sections with undefined FPU state
// should only contain very limited use of floating point instructions
// (notably, use of XMM15 as a zero register).
fninit()
// Need to sync XCR0 with the host, because xsave and xrstor can be
// called from different contexts.
xsetbv(0, sentryXCR0)
// Set the syscall target.
wrmsr(_MSR_LSTAR, kernelFunc(addrOfSysenter()))
wrmsr(_MSR_SYSCALL_MASK, KernelFlagsClear|_RFLAGS_DF)
// NOTE: This depends on having the 64-bit segments immediately
// following the 32-bit user segments. This is simply the way the
// sysret instruction is designed to work (it assumes they follow).
wrmsr(_MSR_STAR, uintptr(uint64(Kcode)<<32|uint64(Ucode32)<<48))
wrmsr(_MSR_CSTAR, kernelFunc(addrOfSysenter()))
}
TEXT ·sysenter(SB),NOSPLIT,$0
-->kernelSyscall(SB)
-->ring0.SaveFloatingPoint(c.floatingPointState.BytePointer())
-->ring0.HaltAndWriteFSBase(regs)
如上实现了虚拟化的系统调用表。这之后就可以猜测,系统调用大部分逻辑可以被重新实现,少数重定位到真实内核中的系统调用。
这里有个疑问,为什么要放在信号里创建VMM, 直接创建也可以吧。可能是容器中启动程序,用信号来劫持容器执行流更方便,有看过的可以交流下。
一些后续的想法
1.能否启动完整的内核?利用kvm我的想法是可以启动完整的内核。因为这里完全可以实现完整的虚拟机管理器,之所以采用简化的内核,可能是考虑到性能问题,毕竟完整的虚拟机还是有一定的消耗的。
2.安全性。安全性有了较大的提升,但还是存在攻破的可能,因为是虚拟化技术,有可能发生逃逸。
3.维护新内核的成本估计也不低。