From c116c4d6ed7330b963ebf1ec92f8e41b3da8f808 Mon Sep 17 00:00:00 2001 From: Thai Duong Date: Tue, 28 Apr 2026 07:22:37 -0700 Subject: [PATCH] add chain.svg --- MADBugs/qemu/README.md | 1 + MADBugs/qemu/WRITEUP.md | 2 + MADBugs/qemu/blog.md | 4 +- MADBugs/qemu/chain.svg | 158 ++++++++++++++++++++++++++++++++++++++++ 4 files changed, 164 insertions(+), 1 deletion(-) create mode 100644 MADBugs/qemu/chain.svg diff --git a/MADBugs/qemu/README.md b/MADBugs/qemu/README.md index 71545a3..bd77d83 100644 --- a/MADBugs/qemu/README.md +++ b/MADBugs/qemu/README.md @@ -20,6 +20,7 @@ PoC video: https://www.youtube.com/watch?v=WWfxGyWoXrc |---|---| | [`blog.md`](blog.md) | the [MAD Bugs blog post](https://blog.calif.io/t/madbugs) | | [`WRITEUP.md`](WRITEUP.md) | full technical advisory: vuln details, five-phase chain, UTM SPICE-safe variant, memfd-layout `g_free` fallback, offsets, prompt log | +| [`chain.svg`](chain.svg) | annotated diagram of the five-phase chain | | [`exploit.c`](exploit.c) | standalone QEMU PoC (homebrew pixman, no SPICE) | | [`exploit_utm.c`](exploit_utm.c) | UTM.app PoC (SPICE-safe, pixman + QEMU fingerprints, `g_free` fallback) | | [`exploit_linux.c`](exploit_linux.c) | Linux aarch64 host variant | diff --git a/MADBugs/qemu/WRITEUP.md b/MADBugs/qemu/WRITEUP.md index 05a0cac..d611f3f 100644 --- a/MADBugs/qemu/WRITEUP.md +++ b/MADBugs/qemu/WRITEUP.md @@ -152,6 +152,8 @@ Vulnerability 1 is the root cause — the integer overflow that creates the size ## Exploitation Strategy +![Exploit chain diagram](chain.svg) + The exploit chains all three vulnerabilities to achieve guest-to-host code execution in five phases. The three vulnerabilities are not independently exploitable in isolation — Vulnerability 1 (the integer overflow) creates the size/dimension mismatch that makes Vulnerabilities 2 and 3 (the OOB read and write) reachable. All three are manifestations of the same root cause: `calc_image_hostmem` computes a small allocation while the rest of virtio-gpu trusts the large logical dimensions. ### Driving virtio-gpu from Userspace diff --git a/MADBugs/qemu/blog.md b/MADBugs/qemu/blog.md index 3d2ef5b..acc690e 100644 --- a/MADBugs/qemu/blog.md +++ b/MADBugs/qemu/blog.md @@ -58,7 +58,9 @@ Pixman dropped its overflow check because the API contract said it could, the sa ## The chain -The bug gives an OOB *write* directly: `transfer_to_host_2d` will happily copy guest-controlled bytes to `pixbuf + x * bpp` for any `x < 0x40000001`. What it does not give you, on its own, is an OOB *read*, which means no ASLR bypass, which means the write is mostly useful for kernel-panicking the host. +![Exploit chain: guest → virtio-gpu overflow → SLIRP/VNC leak → OOB write → system()](chain.svg) + +The bug gives an OOB *write* directly: `transfer_to_host_2d` will happily copy guest-controlled bytes to `pixbuf + x * bpp` for any `x < 0x40000001`. What it does not give you, on its own, is an OOB *read*, which means no ASLR bypass, which means the write is mostly useful for crashing the host process. The way Claude solved the read-primitive problem is, we think, the prettiest part of this exploit, and we want to walk through it because it took us a minute to believe. diff --git a/MADBugs/qemu/chain.svg b/MADBugs/qemu/chain.svg new file mode 100644 index 0000000..133f2b7 --- /dev/null +++ b/MADBugs/qemu/chain.svg @@ -0,0 +1,158 @@ + + + + + + + + + + + + + + + + + + + + + + guest VM + + + exploit (static aarch64, userspace) + RESOURCE_CREATE_2D w=0x40000001 → stride=4, buf=512B + SET_SCANOUT(x=OOB) TRANSFER_TO_HOST_2D(x=OOB) + + + virtio-gpu PCI + + + eth0 (virtio-net) + TCP → 10.0.2.2:5900 + + + + + + + guest / host boundary + + + + QEMU process (host, macOS arm64) + + + + hw/display/virtio-gpu.c + calc_image_hostmem: int overflow + res->width = 0x40000001 (lie) + + + + SLIRP + 10.0.2.2 → host lo + + + + VNC :5900 + FramebufferUpdate + + + + 1 + + + + + + + TRANSFER_TO_HOST_2D: OOB write → _global_impl (dashed: g_free fallback) + + + 4 + + + forward-only OOB, ~4 GB reach → + + + + + + + + pixbuf + 512 B + spray / fillers + ~250 MB mapped zeros + pixman + __TEXT / __DATA + _global_impl + QEMU image + __TEXT / __DATA + la_ptr[g_free] + guest RAM + 512 MB SHM + + + + 2 + SET_SCANOUT(x): surface = pixbuf + x·4 (points into pixman/QEMU) + + + + + 3 + VNC serialises surface → pixel bytes back to guest over SLIRP (the leak) + + + + 5 + RESOURCE_FLUSH → pixman composite dereferences hijacked pointer + + + pixman_image_composite32() + _global_implementation = &fake → wildcard fast_path match + fast_path.func(impl,…) = system("open /*/*/Calc*") + UTM fallback: g_free(ents) = system("open -a Calculator") + + + + Calculator.app + opens on host + + + + virtio control + + leak path (SLIRP→VNC→pixels) + + OOB write + + g_free fallback + + trigger / result + + 1 + overflow resource + 2 + scanout→OOB + 3 + VNC reads host mem as pixels + 4 + OOB write hijack + 5 + composite → system() +