首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >QEMU 虚拟机逃逸漏洞(CVE-2019-14378)漏洞分析

QEMU 虚拟机逃逸漏洞(CVE-2019-14378)漏洞分析

作者头像
用户1423082
发布2024-12-31 20:08:17
发布2024-12-31 20:08:17
19900
代码可运行
举报
文章被收录于专栏:giantbranch's bloggiantbranch's blog
运行总次数:0
代码可运行

这是qemu在网络实现的时候的一个指针错误,当重组大量的ipv4分段数据包时会触发错误,这还是大牛通过代码审计发现的,厉害啊。

漏洞细节

qemu的网络有两部分 1、为虚拟机提供的虚拟网卡(比如PCI网卡) 2、与网络接口控制器交互的网络后端(就是将网络数据包给到主机网络)

默认情况下,QEMU将为guest虚拟机创建SLiRP用户网络后端和适当的虚拟网卡(例如e1000 PCI网卡)

而本漏洞是在SLiRP中的数据包重组中出现的错误。

数据包重组那就需要了解ip分片,ip层的结构如下:

代码语言:javascript
代码运行次数:0
运行
复制
 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version|  IHL  |Type of Service|          Total Length         |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|         Identification        |Flags|      Fragment Offset    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|  Time to Live |    Protocol   |         Header Checksum       |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                       Source Address                          |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Destination Address                        |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|                    Options                    |    Padding    |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+

分片在Flags那里,主要是低3个bit

代码语言:javascript
代码运行次数:0
运行
复制
Bit 0: 保留为, 必须为0
Bit 1: (DF) 0 = 分片, 1 = 不分片.
Bit 2: (MF) 0 =最后一个ip包, 1 = 后面还有分片
Fragment Offset: 13 bits

下面看下相关的结构体

代码语言:javascript
代码运行次数:0
运行
复制
struct mbuf {
    /* header at beginning of each mbuf: */
    struct mbuf *m_next; /* Linked list of mbufs */
    struct mbuf *m_prev;
    struct mbuf *m_nextpkt; /* Next packet in queue/record */
    struct mbuf *m_prevpkt; /* Flags aren't used in the output queue */
    int m_flags; /* Misc flags */

    int m_size; /* Size of mbuf, from m_dat or m_ext */
    struct socket *m_so;

    char *m_data; /* Current location of data */
    int m_len; /* Amount of data in this mbuf, from m_data */

    ...

    char *m_ext;
    /* start of dynamic buffer area, must be last element */
    char m_dat[];
};

mbuf是储存接收到的ip层的信息。有两个buffer,一个是m_dat ,另一个是m_ext,他是m_dat不足以储存的时候,通过在堆上分配内存解决不足的问题

在nat转换的时候,如果传入的数据包是分片的,则应在编辑和重新传输之前重新组装它们。 这个重组由ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp)函数完成。 ip包含当前的IP数据包数据,fp是包含分段数据包的链表。

ip_reass执行以下操作: 1、如果第一个分配的fp为NULL,创建重组队列并将ip插入此队列。 2、检查片段是否与先前收到的片段重复,然后丢弃它。 3、如果收到所有分段的数据包,则重新组装它。 通过修改第一个数据包的头部为新的ip header。

代码语言:javascript
代码运行次数:0
运行
复制
/*
 * Take incoming datagram fragment and try to
 * reassemble it into whole datagram.  If a chain for
 * reassembly of this datagram already exists, then it
 * is given as fp; otherwise have to make a chain.
 */
static struct ip *ip_reass(Slirp *slirp, struct ip *ip, struct ipq *fp)
{

    ...
    ...

    /*
     * Reassembly is complete; concatenate fragments.
     */
    q = fp->frag_link.next;
    m = dtom(slirp, q);

    q = (struct ipasfrag *)q->ipf_next;
    while (q != (struct ipasfrag *)&fp->frag_link) {
        struct mbuf *t = dtom(slirp, q);
        q = (struct ipasfrag *)q->ipf_next;
        m_cat(m, t);
    }

    /*
     * Create header for new ip packet by
     * modifying header of first packet;
     * dequeue and discard fragment reassembly header.
     * Make header visible.
     */
    q = fp->frag_link.next;

    /*
     * If the fragments concatenated to an mbuf that's
     * bigger than the total size of the fragment, then and
     * m_ext buffer was alloced. But fp->ipq_next points to
     * the old buffer (in the mbuf), so we must point ip
     * into the new buffer.
     */
    if (m->m_flags & M_EXT) {
        int delta = (char *)q - m->m_dat;
        q = (struct ipasfrag *)(m->m_ext + delta);
    }

这个漏洞在于计算变量delta的值有问题,而上面这个代码假定了第一个分片数据包不会在external buffer中分配(m_ext)。 当分片数据在mbuf-> m_dat(q将在m_dat内)时,计算q-m-> dat有效(q是包含分片链表和数据包数据的结构)。

否则,如果分配了m_ext缓冲区,则q将位于external buffer ,那么delta的计算就是错误的。

q的数据结构是ipasfrag:就是有一个前向指针跟后向指针,以及包含了一个ip头

代码语言:javascript
代码运行次数:0
运行
复制
/*
 * Ip header, when holding a fragment.
 *
 * Note: ipf_link must be at same offset as frag_link above
 */
struct ipasfrag {
    struct qlink ipf_link;
    struct ip ipf_ip;
};

struct qlink {
    void *next, *prev;
};

我们可以调试看看q的某个时刻的状态是怎样的

代码语言:javascript
代码运行次数:0
运行
复制
gdb-peda$ p *q
$30 = {
  ipf_link = {
    next = 0x7f9e08084ed0,
    prev = 0x7f9e0808487c
  },
  ipf_ip = {
    ip_hl = 0x5,
    ip_v = 0x4,
    ip_tos = 0x0,
    ip_len = 0x8,
    ip_id = 0x7f3a,
    ip_off = 0x8,
    ip_ttl = 0x40,
    ip_p = 0x1,
    ip_sum = 0x95e3,
    ip_src = {
      s_addr = 0xf02000a
    },
    ip_dst = {
      s_addr = 0x202000a
    }
  }
}

可以看到确实是两个ipasfrag前后指针还有ip头信息

我们继续调试运行到下面地方

我们查看下数据结构,可以看到确实此时的q在m_ext的后面,而m_dat在老前面了,那么q - m->m_dat就是负数了

代码语言:javascript
代码运行次数:0
运行
复制
gdb-peda$ p q
$41 = (struct ipasfrag *) 0x7f9e0808882c
gdb-peda$ p *m
$42 = {
  m_next = 0x7f9e080881a0,
  m_prev = 0x7f9e080874c0,
  m_nextpkt = 0x0,
  m_prevpkt = 0x0,
  m_flags = 0xd,
  m_size = 0xcde,
  m_so = 0x0,
  m_data = 0x7f9e08088850 "",
  m_len = 0xc98,
  slirp = 0x563aa67a6380,
  resolution_requested = 0x0,
  expiration_date = 0xffffffffffffffff,
  m_ext = 0x7f9e08088810 "",
  m_dat = 0x7f9e08086eb0 ""
}
gdb-peda$ p *q
$43 = {
  ipf_link = {
    next = 0x7f9e0808487c,
    prev = 0x7f9e08087520
  },
  ipf_ip = {
    ip_hl = 0x5,
    ip_v = 0x4,
    ip_tos = 0x1,
    ip_len = 0xc90,
    ip_id = 0x7f3e,
    ip_off = 0x0,
    ip_ttl = 0x40,
    ip_p = 0x1,
    ip_sum = 0x1c43,
    ip_src = {
      s_addr = 0xffffff8b
    },
    ip_dst = {
      s_addr = 0x0
    }
  }
}

简单的示意图如下:(忽略了分配了m_ext缓冲区,则q将位于external buffer)

代码语言:javascript
代码运行次数:0
运行
复制
+------------------------------+
|                              |
|                              |
|                              |
|                              |
| m_dat 0x7f9e08086eb0         |
|                              |
|                              |
+------------------------------+
|                              |
|m->m_ext 0x7f9e08088810       |
|                              |
|                              |
|q 0x7f9e0808882c              |
|                              |
|                              |
|                              |
+------------------------------+

之后,新计算的指针q被转换为ip结构并且修改部分字段。由于错误地计算了delta,ip将指向不正确的位置,并且ip_src和ip_dst可用于将我们可控的数据写入错误计算的ip的位置。 如果计算出的ip位于没有映射的内存区域,这就会使qemu崩溃。

代码语言:javascript
代码运行次数:0
运行
复制
slirp/src/ip_input.c:ip_reass
    ip = fragtoip(q);   //转换
    ip->ip_len = next;
    ip->ip_tos &= ~1;
    ip->ip_src = fp->ipq_src;
    ip->ip_dst = fp->ipq_dst;

参考

https://blog.bi0s.in/2019/08/24/Pwn/VM-Escape/2019-07-29-qemu-vm-escape-cve-2019-14378/

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
原始发表:2019-10-09,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 漏洞细节
  • 参考
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档