Lucene search

K
seebugRootSSV:92927
HistoryApr 09, 2017 - 12:00 a.m.

Xen: broken check in memory_exchange() permits PV guest breakout(CVE-2017-7228)

2017-04-0900:00:00
Root
www.seebug.org
30

0.001 Low

EPSS

Percentile

41.2%

Detailed analysis: Pandavirtualization: Exploiting the Xen hypervisor

This bug report describes a vulnerability in memory_exchange() that permits PV guest kernels to write to an arbitrary virtual address with the hypervisor privileges. The vulnerability was introduced through a broken fix for CVE-2012-5513 / XSA-29.

The fix for CVE-2012-5513 / XSA-29 introduced the following check in the memory_exchange() hypercall handler:

if ( ! guest_handle_okay(exch. in. extent_start, exch. in. nr_extents) ||
 ! guest_handle_okay(exch. out. extent_start, exch. out. nr_extents) )
{
 rc = -EFAULT;
 goto fail_early;
}

guest_handle_okay() calls array_access_ok(), which calls access_ok(), which is implemented as follows:

/*
 * Valid if in +ve half of the 48-bit address space, or above
 * The Xen-reserved area.
 * This is also valid for range checks (addr, addr+size). As long
 * as the start address is outside the Xen-reserved area then we
 * will access a non-canonical address (and thus fault) before
 * ever reaching VIRT_START.
*/
#define __addr_ok(addr) \
 (((unsigned long)(addr) < (1UL<<47)) || \
 ((unsigned long)(addr) >= HYPERVISOR_VIRT_END))

#define access_ok(addr, size) \
 (__addr_ok(addr) || is_compat_arg_xlat_range(addr, size))

As the comment states, access_ok() only checks the address, not the size, if the address points to the guest memory, based on the assumption that any caller of access_ok() will access guest memory linearly, starting at the supplied address. Callers that want to access a subrange of the memory referenced by a guest of the handle are supposed to use guest_handle_subrange_okay(), which takes an additional start offset parameter, instead of guest_handle_okay().

memory_exchange() uses guest_handle_okay(), but only accesses the guest memory arrays referenced by exch. in. extent_start and exch. out. extent_start starting at exch. nr_exchanged, a 64-bit offset. The intent behind exch. nr_exchanged is that guests always set it to 0 and the nonzero values are only set when a hypercall has to be restarted because of preemption, but this isn’t enforced.

Therefore, by invoking this hypercall with a crafted arguments, it is possible to write to an arbitrary memory location that is encoded as

exch. out. extent_start + 8 * exch. nr_exchanged

where exch. out. extent_start points to guest memory and exch. nr_exchanged is an attacker-chosen 64-bit value.

I have attached a proof of concept. This PoC demonstrates the issue by overwriting the first 8 bytes of the IDT entry for #PF, causing the next pagefault to doublefault. To run the PoC, unpack it in a normal 64-bit PV domain and run the following commands in the domain as root:

root@pv-guest:~# cd crashpoc root@pv-guest:~/crashpoc# make-C /lib/modules/$(uname-r)/build M=$(pwd) make: Entering directory '/usr/src/linux-headers-4.4.0-66-generic' LD /root/crashpoc/built-in. o CC [M] /root/crashpoc/module. o nasm-f elf64-o /root/crashpoc/native. o /root/crashpoc/native. asm LD [M] /root/crashpoc/test. o Building modules, stage 2. MODPOST 1 modules WARNING: could not find /root/crashpoc/. native. o. cmd for /root/crashpoc/native. o CC /root/crashpoc/test. mod. o LD [M] /root/crashpoc/test. ko make: Leaving directory '/usr/src/linux-headers-4.4.0-66-generic' root@pv-guest:~/crashpoc# insmod test. ko root@pv-guest:~/crashpoc# rmmod test

The machine on which I tested the PoC was running the Xen 4.6.0-1ubuntu4 (from Ubuntu 16.04.2). Executing the PoC caused the following console output:

(XEN) *** DOUBLE FAULT*** (XEN)----[ Xen-4.6.0 x86_64 debug=n Tainted: C]---- (XEN) CPU: 0 (XEN) RIP: e033:[<0000557b46f56860>] 0000557b46f56860 (XEN) RFLAGS: 0000000000010202 CONTEXT: hypervisor (XEN) rax: 00007fffe9cfafd0 rbx: 00007fffe9cfd160 rcx: 0000557b47ebd040 (XEN) rdx: 0000000000000001 rsi: 0000000000000004 rdi: 0000557b47ec52e0 (XEN) rbp: 00007fffe9cfd158 rsp: 00007fffe9cfaf30 r8: 0000557b46f7df00 (XEN) r9: 0000557b46f7dec0 r10: 0000557b46f7df00 r11: 0000557b47ec5878 (XEN) r12: 0000557b47ebd040 r13: 00007fffe9cfb0c0 r14: 0000557b47ec52e0 (XEN) r15: 0000557b47ed5e70 cr0: 0000000080050033 cr4: 00000000001506a0 (XEN) cr3: 0000000098e2e000 cr2: 00007fffe9cfaf93 (XEN) ds: 0000 es: 0000 fs: 0000 gs: 0000 ss: e02b cs: e033 (XEN) (XEN) **************************************** (XEN) Panic on CPU 0: (XEN) DOUBLE FAULT -- system shutdown (XEN)**************************************** (XEN) (XEN) Reboot in five seconds...

I strongly recommend changing the semantics of access_ok() so that it guarantees that any access to an address inside the specified range is valid. Alternatively, add some prefix, e.g. “UNSAFE_”, to the names of access_ok() and appropriate wrappers to prevent people from using these functions improperly. Currently, in my opinion, the function name access_ok() is misleading.

I have not allocated a CVE number for this issue.

When disclosing this issue, please credit me as “Jann Horn of Google Project Zero”.

Attachment: xen_memory_exchange_crashpoc. tar