β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-2792

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.

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2792 Β· 11 files
FileTypeDescriptionSize
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).

VERDICT.md
↓ download raw

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_testable
baseline reproduced→ patch + rebuild →patched clean

One-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']
↓ fix.diffper-fix-DF-2792

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.