Resources

People often ask me "How did you learn how to hack?" The answer: by reading. This page is a collection of the blog posts and other articles that I have accumulated over the years of my journey. Enjoy!

From virtio-snd 0-Day to Hypervisor Escape: Exploiting QEMU with an Uncontrolled Heap Overflow- 1920

OtterSecPosted 3 Days Ago
  • QEMU is a machine emulator and virtualizer that let's a host system run guest operating systems of any architecture. For this post, they decided to review Virtio Devices because they require an interface with the host operating system. virtio-snd device buffers are stored in a FIFO linked list after being popped from the virtqueue.
  • In virtio_snd_handle_rx_xfer() there is a code for computing the proper size to use. This takes the size of a buffer and subtracts the size of a struct from it. However, this calculation can underflow by using a small buffer, giving us the first bug. In virtio_snd_pcm_in_cb() the usage of a buffer vs. the allocation is slightly off. First, the allocation size and the bounds check have an 8 byte difference, allowing for an 8 byte OOB write. The final bug was missing bounds check in the edge case of user provided values, creating another OOB write. This happened because the actual buffer allocation size was taken into account.
  • The exploit focuses on the third bug because it contains the largest overflow. Each of these bugs is in the audio input path coming from the host side. So, the OOB write is effectively random. What can we even do with a write with uncontrolled data? Their initial idea was to overwrite a data structure with a size within QEMU but they couldn't find a suitable target. So, they targetted glibc, the heap allocator itself, to corrupt a tcache bin sized chunk size. This allowed them to free a chunk with an oversized entry into the tcache freelist.
  • To perform a heap spray and get a better allocation primitive, they used the paravirtualized filesystem device virtio-9p. With each P9_TXATTRCREATE a host-side buffer is allocated with a name and value field, where the size is arbitrarily controlled. It can be written back to and read through later. An allocation on demand with a choosen size, fully controllable contents and the ability to free as needed. This is perfect for heap exploitation!
  • To use the corruption, they fill up the bins and flush them continuously. Eventally, a write will occur on a 0x200 sized chunk on the size that will make it LARGER than the intended size. Once this is freed into the 0x210-0x2f0 bin it's overallocated. After reurning this chunk, a write to it will corrupt the size of the FOLLOWING chunk ahead of it with a user controlled value of 0x400. Now, the chunk has a complete overlap with the chunk ahead of it. This is a super useful state to be in.
  • To get a heap leak, they use the tcache free list fd pointer. When it's the only entry in the list, safe linking is effectively useless because it's XORed with 0x00. They lost the final 12 bits. With this primitive, they write a pointer to the fd slot that is controlled and then read the memory. At this point, they can just reverse the XOR and read the slot. This effectively creates an encryption oracle to get the bits out. Pretty neat!
  • Using the object V9fsFidState, they were able to produce an arbitrary read/write from the tcache poisioning primitive. By allocating an overlapped chunk with this object, there is a pointer that is directly controllable. This can be used for both reads and writes, which is effectively game over at this point.
  • This bug sat in the codebase for over 2 years but was fixed the same week that OtterSec found it. Still, this is a great example of turning a small bug into something much larger with cascading improvements to exploitability. Great post!