# DF-2937 VERDICT

**Finding:** `sys_sched_setscheduler()` / `ksched_setscheduler()` return
success (0) for invalid policy values; POSIX-required `EINVAL` missing.
**File:** `sys/kern/kern_p1003_1b.c` (syscall layer) +
`sys/kern/kern_sched.c` (callee).
**Severity: Info** — root-gated (verified), pure API-contract defect, no
memory-safety impact.

## Reproduced? — YES (as designed for a conformance bug)

Guest: DragonFly 6.5-DEVELOPMENT #0, X86_64_GENERIC (stock INVARIANTS
kernel), QEMU/KVM. Source tree MD5-identical to the audit tree
(`kern_sched.c` c4ff911406ad45ba964248faae8f9283,
`kern_p1003_1b.c` c15d8af3399595dd2f17e12260aff2d7 before patching).

### Baseline (unpatched)

`run.root.log` (uid 0):

```
sched_setscheduler(0, 0 /*Linux SCHED_OTHER*/, {0}) rc=0 errno=0
sched_setscheduler(0, 4711, {0})                    rc=0 errno=0
sched_setscheduler(0, -1, {0})                      rc=0 errno=0
sched_setscheduler(0, INT_MAX, {0})                 rc=0 errno=0
```

All four bogus policies return success while doing nothing. The hazard is
visible in the same log: after the earlier successful
`sched_setscheduler(0, SCHED_RR, {0})`, the process remains SCHED_RR
(`sched_getscheduler(self)` = 3) across the "successful" bogus-policy call.

Root cause chain, line-accurate:

1. `sys/kern/kern_p1003_1b.c:265-267` — `uap->policy` forwarded to
   `ksched_setscheduler()` without validation.
2. `sys/kern/kern_sched.c:166-195` — `switch (policy)` has cases only for
   SCHED_RR/SCHED_FIFO/SCHED_OTHER and **no `default:`**; `e` stays 0 and
   `return e` (line 197) reports success. POSIX.1b sched_setscheduler(3)
   requires `[EINVAL] The value of the policy parameter is invalid`.

### Privilege gate (bounds severity to Info) — verified

`p31b_proc()` (`sys/kern/kern_p1003_1b.c:136-167`) applies
`CAN_AFFECT(p, p->p_ucred, other_proc)` = `((cr)->cr_uid == 0)`
(`kern_p1003_1b.c:80`) — **including when pid==0 (self)**. `run.unpriv.log`
(uid 1001) shows every pid-taking sched(2) call — even
`sched_getscheduler(0)` — returning `EPERM`, and nonexistent/negative pids
returning `ESRCH`. The complete unprivileged reachable surface of this file
is therefore `sched_yield()` (rc=0) and `sched_get_priority_max/min()`
(validated switch, `EINVAL` for bogus policy, `kern_sched.c:225-266`) —
both memory-safe. No unprivileged route to the defect exists.

## Fix validated? — YES

`fix.diff`: add `default: e = EINVAL; break;` to the policy switch.

Procedure: applied in guest `/usr/src` (`patch -p1`), `make nativekernel`
+ `make installkernel`, rebooted into the patched kernel (uname in
`run.patched.log` header differs: built by root on the same guest).
`BUILD_DONE_OK` in the in-guest `/root/build.log`; full tail saved as
`build.tail.log`.

Result (`run.patched.log`, uid 0):

```
sched_setscheduler(0, 0 /*Linux SCHED_OTHER*/, {0}) rc=-1 errno=22 (Invalid argument)
sched_setscheduler(0, 4711, {0})                    rc=-1 errno=22 (Invalid argument)
sched_setscheduler(0, -1, {0})                      rc=-1 errno=22 (Invalid argument)
sched_setscheduler(0, INT_MAX, {0})                 rc=-1 errno=22 (Invalid argument)
```

Regression check in the same run: valid operations unchanged —
`sched_setscheduler(0, SCHED_RR, {0})` rc=0, `sched_setparam` on an
SCHED_OTHER target still EINVAL (`kern_sched.c:133-134` intent preserved),
unpriv calls still EPERM, `sched_yield()` rc=0. No new warnings; kernel
built with `-Werror`.

## Exploit chain

None — not applicable (Info-severity conformance defect; root-gated).

## Negative results recorded during this pass-2 audit (why the file is otherwise clean beyond known findings)

* **UAF window `FIRST_LWP_IN_PROC` → `LWPHOLD`** (`kern_p1003_1b.c:205-207`
  et al.): killed — every `p_lwp_tree` removal requires `p->p_token`
  (`sys/kern/kern_exit.c:773`, `:1193`), which `p31b_proc()` holds across
  the window; both reapers drain `lwp_lock` (the LWPHOLD count) before
  removal (`kern_exit.c:735-737`, `:1186-1193` — the latter comment
  documents this exact contract).
* **pid validation**: negative/garbage pids mask safely through
  `ALLPROC_HASH(pid) = pid & ALLPROC_HMASK` (`kern_proc.c:63`) → ESRCH;
  SZOMB skipped (`kern_proc.c:524`). Verified empirically (ESRCH for -1,
  -99999999, 999999).
* **Token-order AB-BA** (`p_token` → `lwp_token` here vs `lwp_token` in
  `lwpsignal`, `kern_sig.c:1163-1167`): killed — DFly LWKT tokens are
  deadlock-free by construction (descheduled threads' tokens are
  pullable); the sig path takes lwp_token *instead of*, not nested with,
  p_token.
* **rr_get_interval tick math**: none exists — `ksched_rr_get_interval`
  returns the constant `{0, 100000000}` (`kern_sched.c:63-64,272-278`);
  its copyout *is* checked (`kern_p1003_1b.c:348-349`), unlike
  getparam's (DF-0171).
* **Policy mapping table**: `getscheduler` is a 3-case switch, not a table
  (`kern_sched.c:103-121`) — no index bound issue.
* **sched_param ABI**: kernel and userland share `sys/sys/sched.h:47-50`
  (4-byte struct defined outside any `_KERNEL` guard) — no size mismatch.
* **Module unload UAF**: `ksched_detach` has no caller (dead code; file is
  compiled-in `standard`, `sys/conf/files:1971`), `ksched` singleton never
  freed.
* **Known, not re-reported**: DF-0170 (ignored copyin ×2, incl.
  setscheduler:258), DF-0171 (ignored copyout:245), DF-0223
  (SCHED_OTHER prio bounds, `kern_sched.c:181-184`), DF-0224 (getparam
  uninit leak, `kern_sched.c:142-150` — demonstrated again in
  `run.root.log` via `sched_getparam(1)` rc=0), DF-0225 (lwp_rtprio
  locking contract).
