DF-2792 / ioprio_pgrp.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 | /* * DF-2792 PoC — sys_ioprio_get(PRIO_PGRP) compares p->p_nice but * assigns p->p_ionice (sys/kern/kern_resource.c:365-369): * * LIST_FOREACH(p, &pg->pg_members, p_pglist) { * if (PRISON_CHECK(curtd->td_ucred, p->p_ucred) && * p->p_nice > high) <-- WRONG field * high = p->p_ionice; * } * * The PRIO_PROCESS (:340-352) and PRIO_USER (:399-413) paths compare * (and assign) p_ionice. The intent of the pgrp scan is to return the * maximum ionice among the group's members; instead it gates each * member on that member's CPU nice, so members whose nice does not * exceed the running "high" are skipped and the returned value is * order-dependent garbage (can also stay at the sentinel IOPRIO_MIN-2 * and misreport ESRCH when all members have nice <= -1). * * Deterministic demo (no race): parent + 2 children in one pgrp. * parent: nice 0, ionice 0 (joins pgrp first => iterated first) * childA: nice 5, ionice 2 * childB: nice 1, ionice 8 * buggy walk: high=-1 -> parent(nice 0 > -1) high=0 * -> childA(nice 5 > 0) high=2 * -> childB(nice 1 > 2? NO) skipped => returns 2 * correct answer (max ionice) = 8, which ioprio_get(PRIO_USER) returns. */ #include <sys/types.h> #include <sys/resource.h> #include <sys/syscall.h> #include <sys/wait.h> #include <unistd.h> #include <stdio.h> #include <stdlib.h> #include <string.h> #include <errno.h> #include <signal.h> #ifndef PRIO_PGRP #define PRIO_PGRP 1 #endif #ifndef PRIO_USER #define PRIO_USER 2 #endif int main(void) { pid_t a, b, me = getpid(); int pgid, ra, rb, rp, ru, pgrp_res; int nice_a = 5, nice_b = 1; int ion_a = 2, ion_b = 8; if (setpgid(0, 0) != 0) { perror("setpgid"); return 1; } pgid = getpgrp(); a = fork(); if (a == 0) { setpriority(PRIO_PROCESS, 0, nice_a); pause(); _exit(0); } b = fork(); if (b == 0) { setpriority(PRIO_PROCESS, 0, nice_b); pause(); _exit(0); } /* give the children time to apply their nice values */ sleep(1); if (ioprio_set(PRIO_PROCESS, a, ion_a) != 0) { perror("ioprio_set A"); return 1; } if (ioprio_set(PRIO_PROCESS, b, ion_b) != 0) { perror("ioprio_set B"); return 1; } if (ioprio_set(PRIO_PROCESS, me, 0) != 0) { perror("ioprio_set parent"); return 1; } ra = ioprio_get(PRIO_PROCESS, a); rb = ioprio_get(PRIO_PROCESS, b); rp = ioprio_get(PRIO_PROCESS, me); errno = 0; pgrp_res = ioprio_get(PRIO_PGRP, pgid); if (pgrp_res == -1 && errno) printf("ioprio_get(PGRP) FAILED: %s\n", strerror(errno)); errno = 0; ru = ioprio_get(PRIO_USER, getuid()); if (ru == -1 && errno) printf("ioprio_get(USER) FAILED: %s\n", strerror(errno)); printf("members ionice: parent=%d childA(pid %d)=%d childB(pid %d)=%d\n", rp, a, ra, b, rb); printf("ioprio_get(PRIO_PGRP, %d) = %d (correct max = %d)\n", pgid, pgrp_res, ion_b); printf("ioprio_get(PRIO_USER, uid) = %d\n", ru); kill(a, SIGKILL); kill(b, SIGKILL); waitpid(a, NULL, 0); waitpid(b, NULL, 0); if (pgrp_res != ion_b) { printf("REPRODUCED: pgrp result %d != expected max %d " "(copy-paste of p_nice into the p_ionice comparison)\n", pgrp_res, ion_b); return 0; } printf("NOT reproduced (pgrp returned the correct max)\n"); return 1; } |