sys_ioprio_get(PRIO_PGRP) compares p_nice but assigns p_ionice β wrong (order-dependent) result and possible spurious ESRCH
| Field | Value |
|---|---|
| ID | DF-2792 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:N |
| CWE | CWE-682 Incorrect Calculation |
| File | sys/kern/kern_resource.c |
| Lines | 366-368 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | base:kern |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
The PRIO_PGRP scan of sys_ioprio_get() gates each member on p->p_nice (CPU nice, β20..20) while assigning p->p_ionice (1..10); the PROCESS and USER paths both compare p_ionice. The returned "highest ionice" is whatever member's nice happens to exceed the running high β deterministic PoC: members ionice {1,2,8} with nice {0,5,1} returned 2 instead of 8 while PRIO_USER correctly returned 8. When no member's nice exceeds the sentinel the call returns ESRCH despite live members.
Recommended fix
Compare p->p_ionice > high in the member scan (copy-paste from
getpriority's p_nice).
Timeline
- 2026-08-31 Discovered during pass-2 audit of kern_resource.c (GLM 5.3); deterministic PoC reproduced same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2792 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| ioprio_pgrp.c | β | 3.3 KB | view raw | |
| build.sh | β | 81 B | view raw | |
| run.sh | β | 31 B | view raw | |
| run.log | β | 284 B | view raw | |
| run.2.log | β | 251 B | view raw | |
| env.txt | β | 253 B | view raw | |
| fix.diff | β | 340 B | view raw | |
| README.md | β | 1.2 KB | β raw | |
| VERDICT.md | β | 2.0 KB | β raw | |
| manifest.json | β | 729 B | view raw | |
| verdict.json | β | 2.2 KB | view raw |
DF-2792 β sys_ioprio_get(PRIO_PGRP) compares p_nice but assigns p_ionice
What
Copy-paste bug at sys/kern/kern_resource.c:365-369: the PRIO_PGRP scan of
sys_ioprio_get() gates each member on p->p_nice (the CPU nice) but
assigns p->p_ionice, while the PRIO_PROCESS (:340-352) and PRIO_USER
(:399-413) paths compare p_ionice. The intended result is the maximum
ionice of the group; the actual result is order-dependent garbage, and the
call can even return ESRCH despite live members (when no member's nice
exceeds the running high / the sentinel IOPRIO_MIN-2 = -1).
Build
cc -O2 -o ioprio_pgrp ioprio_pgrp.c
Run (unprivileged, deterministic, no race)
./ioprio_pgrp
Expected output
members ionice: parent=1 childA(pid A)=2 childB(pid B)=8 ioprio_get(PRIO_PGRP, P) = 2 (correct max = 8) ioprio_get(PRIO_USER, uid) = 8 REPRODUCED: pgrp result 2 != expected max 8
Reproduced identically on the stock kernel (#0) and on the DF-2791-patched kernel (#1) β independent of that fix.
Fix
fix.diff β compare p->p_ionice instead of p->p_nice (one line).
Authored post-verification; kernel rebuild for this one-liner was not
performed (fix_status: not_testable).
DF-2792 VERDICT
Finding: sys_ioprio_get() PRIO_PGRP member scan uses the wrong field
in its comparison (sys/kern/kern_resource.c:366-368).
Status: REPRODUCED (deterministic, unprivileged). Severity Low (correctness bug in a query API; no memory-safety impact).
Root cause
LIST_FOREACH(p, &pg->pg_members, p_pglist) {
if (PRISON_CHECK(curtd->td_ucred, p->p_ucred) &&
p->p_nice > high) /* <-- should be p->p_ionice */
high = p->p_ionice;
}
high is initialized to IOPRIO_MIN-2 (-1). p_nice is the CPU nice
(-20..20, kern_resource.c donice :301-307); p_ionice the I/O nice
(1..10, doionice :539-548). The USER-path callback and the PROCESS path
both compare p_ionice; only the PGRP path compares p_nice, so members
are admitted/skipped based on an unrelated field and the returned "max"
is whatever the interleaving of nice vs running-high produces:
order-dependent, wrong, or the sentinel (-1) β spurious ESRCH (:385-388)
when every member's nice fails the comparison.
Run
Guest dfbsd 6.5-DEVELOPMENT X86_64_GENERIC #0, uid 1001. Parent+2 children in one pgrp; parent ionice 0 (clamped to 1), childA nice 5/ionice 2, childB nice 1/ionice 8:
ioprio_get(PRIO_PGRP, 965) = 2 (correct max = 8) ioprio_get(PRIO_USER, uid) = 8 <- correct path proves the intent
Reproduced twice (run.log on stock kernel; run.2.log on the kernel patched for DF-2791 β unrelated file regions).
Impact ceiling
Wrong query result for an unprivileged, jail-aware (PRISON_CHECK intact) API: userland scheduling heuristics get a wrong I/O priority for a process group. No disclosure (values belong to visible processes only), no corruption, no privilege interaction. Low severity.
Fix validation
fix.diff (one-line: p->p_nice β p->p_ionice in the comparison).
Not rebuilt into a kernel β deterministic userspace-visible behavior,
one-line change, validated by inspection against the correct USER/PROCESS
paths (fix_status: not_testable).
Fix verification
not_testableOne-line field fix authored (fix.diff); dedicated kernel rebuild not performed for a Low correctness finding β behavior validated by inspection against the correct PROCESS/USER paths in the same function.
['fix.diff']
Confirmed kernel references
Detail
Evidence (decisive lines)
["run.log / run.2.log: 'ioprio_get(PRIO_PGRP, 965) = 2 (correct max = 8)' with USER path returning 8"]
PoC changes
Dropped local ioprio_set/ioprio_get wrappers (DF libc already declares them in sys/resource.h), added signal.h; syscall numbers 520/521 from syscalls.master.
Verified recommended fix
Compare p->p_ionice instead of p->p_nice in the ioprio_get PRIO_PGRP member scan (fix.diff).
Verdict
sys_ioprio_get()'s PRIO_PGRP scan (kern_resource.c:366-368) gates members on p->p_nice while assigning p->p_ionice; the PROCESS/USER paths compare p_ionice. Deterministic unprivileged PoC: pgrp members with ionice {1,2,8} and nice {0,5,1} yields ioprio_get(PRIO_PGRP)=2 while ioprio_get(PRIO_USER)=8, and the call can even return ESRCH with live members when no member's nice exceeds the running high. Pure correctness defect in a query API (wrong I/O-priority hint to userland); no memory safety, disclosure, or privilege impact. Reproduced identically on stock kernel #0 and the DF-2791-patched kernel #1.
No comments yet.