QEMU和KVM作为虚拟化技术的典型代表,被广泛的应用在各家厂商的云计算系统中。作为一款有着十多年历史的软件,QEMU一直遭受着安全问题的困扰。随着以QEMU/KVM虚拟化软件为基础的云计算的不断发展,其安全问题在这几年备受关注。
虚拟化的主要思想是,通过分层将底层的复杂、难用的资源虚拟抽象成简单易用的的资源,提供给上层使用。本质上,计算机的发展过程也是虚拟化不断发展的过程。一个简单的例子:Python、Java等高级语言脱离了机器码,重新定义了自己的指令,由各平台的虚拟机去解释执行,实现了完全的跨平台。另一个例子:TCP/IP协议栈模型,网卡设备传递的都是二进制数据,通过网络层、传输层的抽象之后,应用程序不需要直接跟网络数据包打交道,只需要关心协议栈最上层的接口,也不需要关心其他使用网卡设备的程序,只需要将要发送的数据和地址提供给协议栈,协议栈会自动处理好IP路由、分片等细节。
计算机中每个进程都是对计算机的抽象,每个进程都认为自己独占整个计算机系统的资源,但这些都是操作系统给进程的假象。我们可以把进程理解为一种虚拟机,操作系统通过底层资源,使得进程可以使用整个计算机的物理资源。
模拟器是另一种形式的虚拟机,比如模拟器可以将ARM程序指令解释为x86相同功能的指令,以此来直接运行在硬件CPU上。典型的模拟器有QEMU、Bochs等。
高级语言虚拟机在模拟器的基础上更进一步,比如Python、Java等语言,由它们的编译器将代码转换为字节码,这种字节码是一种自定义的指令集,任何想要运行这些程序的平台都需要安装对应的虚拟机。常见的高级语言虚拟机有Java的JVM、Python虚拟机等。这些虚拟机的指令都是公开的,任何人都通过指令可以写出反编译工具。假如在公开的指令上进行修改,那么公开的虚拟机就无法解码,需要根据自定义的指令集进行解码,这就是软件保护中虚拟机保护的原理。
进程、模拟器、高级语言虚拟机提供的都是指令的执行环境,而系统虚拟机提供的是一个完整的系统环境,通过虚拟化技术虚拟CPU、内存、外设等,并且虚拟机直接可以实现完整隔离,这种管理全局物理资源的软件叫做虚拟机监视器(Virtual Machine Monitor)VMM。它利用时分复用或者空分复用的办法将硬件资源在各个虚拟机之间分配。典型的虚拟化解决方案有Vmware、QEMU、Virtual Box、HyperV等。
虚拟化安全的目的是保证各个虚拟机相互独立、虚拟机与虚拟机和宿主机之间的安全边界不会被破坏。虚拟化平台的安全设计的内容很多,如虚拟机读取宿主机内存的数据、虚拟机无限制的消耗宿主机的资源、虚拟机中执行特殊的指令导致宿主机崩溃、虚拟机中普通用户通过虚拟化平台进行权限提升,当然,最严重的还是虚拟机逃逸,也就是虚拟机中的用户通过执行一段代码利用虚拟化平台的漏洞来在宿主机上执行代码。
虚拟化平台在云计算中是一个非常重要的基础组件,其上运行着大量不受信任的租户的虚拟机,租户可以在虚拟机中执行任意的代码,所以确保虚拟化平台的安全是云计算平台非常重要的任务。
安全漏洞大部分上都是由不受信任的输入造成的,这种不信任的输入也叫做攻击面。QEMU的输入大体来自两个方面,即虚拟机内部和虚拟机外部。
来自虚拟机内部的攻击指的是虚拟机内部用户执行各种恶意请求,利用QEMU/KVM的漏洞来对QEMU进行攻击。虚拟机内部用户能够执行任意代码,访问任意的虚拟设备及虚拟物理资源,所以从QEMU的角度来看,来自虚拟机内部的所有请求数据都是不可信的。大体上,虚拟机内部的攻击包括执行特殊的指令、读取特殊的寄存器、与模拟设备进行交互。其中设备模拟是最大的攻击面,QEMU中的大多数安全漏洞都是由设备模拟造成的。设备模拟包括传统全虚拟化设备模拟以及半虚拟化的virtio、vhost设备模拟。
虚拟机内部攻击还有一个重要来源是虚拟机内部的agent,通常来将这类agent是用来为虚拟机与宿主机通信提供方便的,如建立特殊的通道用来方便虚拟机与宿主机共享剪切板等。虚拟化软件通常需要处理大量来自虚拟机内部的输入,如果在处理这些输入请求的时候没有进行完整性的安全校验,则容易导致漏洞的产生,虚拟机内部用户可以利用这些漏洞对虚拟化软件进行攻击。商业化虚拟化软件上的这类agent功能比较复杂,如VirtualBox的Guest Addition和VMware的vmtools;QEMU虚拟机上的agent功能比较简单,所以漏洞相比VirtualBox和VMware来说也要少很多。
虚拟机外部的攻击是指与QEMU交互的其他组件构造恶意数据,利用QEMU的漏洞来对QEMU进行攻击。这类外部组件有很多,如VNC、SPICE等能够远程连接QEMU的组件。VNC和SPCIE都是远程的桌面协议,以VNC为例,QEMU内置了一个VNC服务器,运行各种远程的VNC客户端对其进行连接,从而让远程用户能够访问虚拟机。
QEMU的VNC服务器端和不受信任的客户端会进行各种数据交互,如VNC协议、传递鼠标和键盘数据信息,如果在这个过程中QEMU没有正确的进行各种安全性校验就会产生安全漏洞,远程客户端通过构造恶意数据就能够对QEMU进行攻击。外部攻击的另一个方面是虚拟机访问的文件,如虚拟机所使用的镜像。QEMU支持多种镜像,在打开镜像的时候会进行各种解析,如果解析代码没有进行完整性的安全校验就可能由漏洞产生,用户可以通过构造一个恶意的镜像触发解析漏洞,从而对QEMU进行攻击。
QEMU安全漏洞主要分为两个方面:设备模拟漏洞和外部漏洞。
设备模拟漏洞是到目前为止虚拟化平台出现最多的漏洞,QEMU、VirtualBox、VMware等虚拟化软件都出现过大量由设备模拟产生的漏洞。虚拟设备漏洞之所以如此众多,一方面是由于虚拟化平台需要模拟大量的设备,以呈现一个完整的硬件平台到虚拟机中,零一方面是由于虚拟机内部需要与虚拟设备进行大量交互,如果虚拟化软件没有对虚拟机传过来的数据进行完整性的安全检查就会导致安全漏洞的产生。
虚拟机与虚拟设备的交互是通过硬件使用的I/O端口或者PCI设备的MMIO完成的,QEMU在虚拟机启动时为虚拟机模拟出整个硬件体系以及各种设备,指定各个设备需要的I/O端口和MMIO,当SeaBIOS启动之后,会为设备分配具体的资源,比如具体设备使用的I/O端口或者MMIO的地址。虚拟机内核在启动的时候,硬件驱动会扫描设备,为设备加载对应的驱动,这样虚拟机就能够正常的对这些端口或者MMIO地址空间进行读写访问。
每个设备都会有读写I/O端口或者MMIO地址空间的回调函数,每当虚拟机内部操作系统在读写这些区域的时候,虚拟机会都产生VM Exit,陷入KVM,然后KVM会把这些请求分派到QEMU,QEMU就会调用这些回调函数来完成虚拟机对设备的模拟访问,更新虚拟设备的状态,QEMU可能也会访问实际的物理设备。
由于编写模拟设备的人员众多,加上设备的接口大多数比较复杂,因此QEMU经常在处理这些读写请求的时候没有完整的对请求数据进行安全校验,导致产生了很多安全性问题,其数据攻击流如下图:
接下来以一个具体例子来对模拟设备的漏洞进行分析。这里分析QEMU 3.1产生的一个漏洞。pm_smbus_init
函数初始化SMBus
的模拟,其中注册了一个MMIO地址空间,其中的地址空间MemoryRegionOps
为pm_smbus_ops
,代码如下:
// qemu-3.1.0-rc4/hw/i2c/pm_smbus.c
static const MemoryRegionOps pm_smbus_ops = {
.read = smb_ioport_readb,
.write = smb_ioport_writeb,
.valid.min_access_size = 1,
.valid.max_access_size = 1,
.endianness = DEVICE_LITTLE_ENDIAN,
void pm_smbus_init(DeviceState *parent, PMSMBus *smb, bool force_aux_blk)
smb->reset = pm_smbus_reset;
smb->smbus = i2c_init_bus(parent, “i2c”);
smb->smb_auxctl |= AUX_BLK;
memory_region_init_io(&smb->io, OBJECT(parent), &pm_smbus_ops, smb,
|
当虚拟机对该地址空间进行读写的时候会相应的调用smb_ioport_readb或者smb_ioport_writeb函数,这里分析后者。
// qemu-3.1.0-rc4/hw/i2c/pm_smbus.c
static void smb_ioport_writeb(void *opaque, hwaddr addr, uint64_t val,
SMBUS_DPRINTF(“SMB writeb port=0x%04” HWADDR_PRIx
” val=0x%02″ PRIx64 “n”, addr, val);
s->smb_stat &= ~(val & ~STS_HOST_BUSY);
if (!s->op_done && !(s->smb_auxctl & AUX_BLK)) {
uint8_t read = s->smb_addr & 0x01;
if (!read && s->smb_index == s->smb_data0) {
s->smb_data[s->smb_index] = s->smb_blkdata;
s->smb_stat |= STS_BYTE_DONE;
} else if (s->smb_ctl & CTL_LAST_BYTE) {
s->smb_blkdata = s->smb_data[s->smb_index];
s->smb_stat &= ~STS_HOST_BUSY;
s->smb_blkdata = s->smb_data[s->smb_index];
s->smb_stat |= STS_BYTE_DONE;
s->set_irq(s, smb_irq_value(s));
|
在处理SMBHSTSTS
这个虚拟机内部发过来的命令的时候,在一定条件(虚拟机内部可以控制该条件)下会将s->smb_index
进行自增操作,但是这里并没有对其进行安全检查,如果虚拟机内部的恶意用户一直增加该值,它会变成一个非常大的值,在SMBus
的模拟中,smb_index
用来索引s->smb_data
数组,而该数组大小只有PM_SMBUS_MAX_MSB_SIZE(32)
个字节。所以,如果smb_index
增大到超过32的时候就会访问到smb_data
数组定义之后的数据。在处理SMBHSTSTS
这个命令的后面可以看到,既有对s->smb_data[s->smb_index]
的读操作,也有写入该地址的操作。所以该漏洞会导致虚拟机对QEMU地址空间的s->smb_data
之后的内存进行任意读写,由于smb_index
是32位数据,所以理论上能读写的地址空间为4GB。通过越界读来获取QEMU的内存地址分布信息,绕过ASLR,通过写控制QEMU进程的PC指针,实现代码流控制。
事实上,该漏洞能够实现完美的虚拟机逃逸,相比2015年的venom漏洞只是越界写不同,该漏洞能够稳定的读QEMU数据,从而绕过ASLR。不过由于该漏洞在QEMU3.1版本的最开始引入,在3.1发布前最后一刻被修补,所以该漏洞没有存在于任何发行版的QEMU中,但是毫无疑问,该漏洞是QEMU历史上最为严重的漏洞之一。
其修补很简单,只需要在自增之后做一个长度判断即可。
if(s->smb_index >= PM_SMBUS_MAX_MSG_SIZE){
|
虽然设备模拟都漏洞占了所有QEMU漏洞都大部分,但是QEMU本身还存在其他都一些攻击面,如VNC。VNC是一个基于RFB协议都远程桌面共享系统。QEMU内建了一个VNC服务器,用来接收客户端的请求,所有的VNC客户端都可以用来跟QEMU虚拟机连接。客户端通过RFB协议向服务器端传递鼠标键盘等控制信息,服务器端通过这些信息来更新虚拟机的相应信息。QEMU服务器端在处理客户端发送过来的数据时如果存在漏洞,则恶意的客户端可能发送构造的数据触发这些漏洞,导致对QEMU的攻击。
CVE-2015-8504就是这样一个典型的例子。该漏洞发生在QEMU处理SetPixelFormat
消息的时候,产生漏洞的代码如下。
static void set_pixel_format(VncState *vs,
int bits_per_pixel, int depth,
int big_endian_flag, int true_color_flag,
int red_max, int green_max, int blue_max,
int red_shift, int green_shift, int blue_shift)
vs->client_pf.rmax = red_max;
vs->client_pf.rbits = hweight_long(red_max);
vs->client_pf.rshift = red_shift;
vs->client_pf.rmask = red_max << red_shift;
vs->client_pf.gmax = green_max;
vs->client_pf.gbits = hweight_long(green_max);
vs->client_pf.gshift = green_shift;
vs->client_pf.gmask = green_max << green_shift;
vs->client_pf.bmax = blue_max;
// qemu-2.4.0/ui/vnc-enc-tight.c
static void write_png_palette(int idx, uint32_t pix, void *opaque)
color->red = ((red * 255 + vs->client_pf.rmax / 2) /
color->green = ((green * 255 + vs->client_pf.gmax / 2) /
color->blue = ((blue * 255 + vs->client_pf.bmax / 2) /
|
set_pixel_format
函数用来处理VNC_MSG_CLIENT_SET_PIXEL_FORMAT
消息,其参数red_max
、green_max
、blue_max
都来自远程客户端,可以是任意值,将这些值赋值到vs->client_pf
相应的成员中。其值在随后的write_png_palette
函数调用中被用来做被除数,如果远程恶意客户端把red_max
设置为0,那么write_png_palette
函数中就会就会产生除0错误,导致虚拟机崩溃。
该漏洞的修补也比较简单,只需要在赋值的时候把0改成0xff即可。
vs->client_pf.rmax = red_max ? read_max : 0xff;
vs->client_pf.gmax = green_max ? green_max : 0xff;
vs->client_pf.bmax = blue_max ? blue_max : 0xff;
|
其他的远程桌面协议也有类似问题,如SPICE的CVE-2016-9578漏洞,就是对于客户端校验不严格产生的整数溢出漏洞。
随着2015年QEMU中venom漏洞都曝光以及随后各类安全比赛中加入VirtualBox、VMware、HyperV等虚拟化软件项目,越来越多的人开始对虚拟化的安全进行系统深入的研究,也有越来越多的虚拟化平台被发现漏洞。从前面几个漏洞例子可以看出,QEMU威胁最大、漏洞最多的地方在设备模拟。对于其他虚拟化软件来说,设备模拟同样是漏洞重灾区。设备模拟漏洞产生的原因都是类似的,本质上都是没有对虚拟机中的请求进行完整性的安全校验,在一种虚拟化软件中存在漏洞的设备也有可能在另一种虚拟化软件中同样存在。如e1000网卡设备是QEMU和VirtualBox默认使用的网卡,研究QEMU下的e1000网卡模拟实现同样有助于研究VirtualBox下的实现。
为了从根本上解决虚拟化软件的安全漏洞,目前可以从下面几个方面入手。
减少攻击面。一方面可以将一些设备模拟放到内核中去,内核的代码会经过严格的安全审计,如现在的APIC和I/O APIC中断控制器就是默认在内核中模拟的;另一方面是尽量减少模拟设备的使用。
使用新的、轻量级的虚拟化解决方案。在KVM诞生之初,选择QEMU作为其方案的应用层软件在当时看来是比较合理的。但是在目前看来QEMU由于其自身历史原因,希望实现大而全的对各个平台的模拟,而不仅限于云计算,所以带来了一些“包袱”。很多的厂商正在致力于减少这些“包袱”,如Intel提出了nemu,其目标是作为一个云计算专用的虚拟化平台。它在QEMU的基础上进行了裁剪,剔除了除x86和ARM架构外的其他平台模拟,也剔除了一些不常用的设备模拟,而只使用最基本的、必不可少的功能。
使用Rust等现代内存安全编程语言。目前各个虚拟化平台都使用C/C++语言完成,非常容易产生安全漏洞。使用内存安全编程语言来编写虚拟化软件也逐渐发展起来,比如Google的crosvm就使用了Rust创建的虚拟化软件平台,Amazon则是将上述两个方面结合起来,在crosvm的基础上开发了Firecracker。
《QEMU/KVM源码解析与应用》——李强 编著
原文始发于微信公众号(山石网科安全技术研究院):QEMU虚拟化安全的攻击面探索与思考