DF-2937 / verdict.json
{ "finding_id": "DF-2937", "status": "reproduced", "reproduced": 1, "impact": "none", "confidence": "certain", "verdict": "sys_sched_setscheduler (sys/kern/kern_p1003_1b.c:265-267) forwards the unvalidated policy int to ksched_setscheduler, whose switch (sys/kern/kern_sched.c:166-195) has no default case, so any policy outside {SCHED_FIFO=1, SCHED_OTHER=2, SCHED_RR=3} returns 0 (success) as a silent no-op, where POSIX requires EINVAL. Demonstrated live on the stock X86_64_GENERIC guest as root: policies 0, 4711, -1, INT_MAX all rc=0, and a process left at SCHED_RR stays real-time across a 'successful' bogus-policy call. The defect is root-gated: CAN_AFFECT (kern_p1003_1b.c:80) is cr_uid==0 even for pid==0, and the unpriv probe shows every pid-taking sched(2) call returning EPERM, so the complete unpriv reachable surface (sched_yield, sched_get_priority_max/min) is memory-safe. Info-severity API-contract defect, no memory-safety impact, no exploit chain.", "exploit_chain": "", "evidence": [ "run.root.log (baseline kernel #0): lines 'sched_setscheduler(0, {0,4711,-1,INT_MAX}, {0}) ... rc=0 errno=0' - success for invalid policies; 'sched_getscheduler(self) rc=3' after bogus-policy call shows process still SCHED_RR", "run.unpriv.log (uid 1001): every pid-taking sched(2) call EPERM incl. pid==0; negative/nonexistent pids ESRCH - root gate bounds severity", "run.patched.log (kernel #1 built with fix.diff): same four calls now rc=-1 errno=22; valid SCHED_RR set still rc=0; setparam-on-SCHED_OTHER still EINVAL - fix changes exactly the broken behavior, no regression", "build.log: full nativekernel build output, BUILD_DONE_OK, -Werror clean", "VERDICT.md: root-cause chain with path:line + the pass-2 negative results (UAF window killed via kern_exit.c:773/1193 p_token contract)" ], "kernel_refs": [ "sys/kern/kern_p1003_1b.c:265-267", "sys/kern/kern_sched.c:166-195", "sys/kern/kern_sched.c:197", "sys/kern/kern_p1003_1b.c:80", "sys/kern/kern_p1003_1b.c:136-167" ], "poc_changes": "Wrote probe from scratch (no seed existed): exercises all 8 syscalls across pid={0,self,other,nonexistent,negative} as both uid 1001 and uid 0; had to push source via scp because vm.sh run_user/run_root pipe the script through stdin.", "attempts": 1, "guest_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #0: Thu Jul 2 06:02:54 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64 (baseline) / #1: Thu Sep 3 17:37:43 UTC 2026 (fix-validated)", "runtime_sec": 2100, "guest_dirty": 0, "build_cmd": "cc -o /root/sched_probe /root/sched_probe.c (guest) ; fix kernel: cd /usr/src && patch -p1 < /root/fix.diff && make nativekernel && make installkernel", "run_cmd": "/tmp/sched_probe (as uid 1001 and as root)", "code_hash": "47a0cfc92665b67d9798ccc5ab4989885a5115c44336a4916d2a3513e711c0f5", "notes": "Known findings not re-reported: DF-0170 (ignored copyin, kern_p1003_1b.c:202/258), DF-0171 (ignored copyout :245), DF-0223 (SCHED_OTHER prio bounds, kern_sched.c:181-184), DF-0224 (getparam uninit stack copyout, kern_sched.c:142-150 - still observable in run.root.log via sched_getparam(1) rc=0), DF-0225 (lwp_rtprio locking contract). Guest returned via vm.sh reset with-src.", "recommended_fix": "Add 'default: e = EINVAL; break;' to the policy switch in ksched_setscheduler (sys/kern/kern_sched.c) - one line, validated by full kernel rebuild.", "fix_status": "fixed", "fix_kernel_uname": "DragonFly dfbsd 6.5-DEVELOPMENT #1: Thu Sep 3 17:37:43 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64", "fix_baseline_reproduced": 1, "fix_patched_reproduced": 0, "fix_verdict": "Applied fix.diff (one-line default: EINVAL) to guest /usr/src, rebuilt with make nativekernel (17087-line -Werror-clean build log, BUILD_DONE_OK), installkernel + reboot into kernel #1. Re-running the identical probe: all four invalid policies now return EINVAL (rc=-1 errno=22); valid-path behavior unchanged (SCHED_RR set succeeds, SCHED_OTHER-target setparam still EINVAL, unpriv still EPERM, yield rc=0). Baseline defect gone, no regression.", "fix_evidence": [ "fix.diff", "build.log (BUILD_DONE_OK)", "run.patched.log vs run.root.log" ] } |