prison_priv_check allows SYSCAP_NOSETTIME in jails β jailed uid-0 sets the HOST system clock (settimeofday/clock_settime/adjtime/ntp_adjtime)
| Field | Value |
|---|---|
| ID | DF-2800 |
| Status | new |
| Severity | Medium |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:H/UI:N/S:C/C:N/I:H/A:L |
| CWE | CWE-863 Incorrect Authorization |
| File | sys/kern/kern_jail.c |
| Lines | 891-892 (leaf), 858-861 (group-2 blanket; caps rewrite kern_caps.c:336-348) |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-08-31 |
| Pass | 2 (GLM 5.3 second pass) |
| Bucket | privesc |
| Reported | pending |
| Known CVE | none |
| CVE match | novel |
Summary
prison_priv_check() returns 0 (allow) for SYSCAP_NOSETTIME and blanket-allows the whole group-2 meta capability SYSCAP_SENSITIVEROOT β which is what caps_priv_check actually delivers because kern_caps.c rewrites SYSCAP_NOSETTIME to its group number before calling prison_priv_check. Jailed uid-0 therefore passes the gate in sys_settimeofday, kern_clock_settime, sys_adjtime and ntp_adjtime. The clock is host-global; settime() only consults the global securelevel and pr_securelevel is dead.
Threat model & preconditions
Any jail tenant holding uid 0 (the normal multi-tenant model) skews/steps/freezes the host clock β breaking NTP, TLS/x509 and kerberos validity windows, cron, DB timestamps and log forensics for the host and every co-resident jail. FreeBSD denies this in jails; DragonFly allows it.
Proof of contest
VERIFIED on the stock guest (findings/poc/DF-2800/jailsettime.c): a child that jailed itself with an ordinary jail(2) v1 call stepped the host clock backward 30 seconds (host parent observed β28s; clock restored by the PoC). Fixed kernel returns EPERM with the clock unchanged. No uid0 route (integrity isolation break).
Recommended fix
Deny SYSCAP_NOSETTIME in prison_priv_check (return EPERM) and pass the original capability (not the rewritten group meta) from kern_caps.c β two hunks in findings/poc/DF-2800/fix.diff.
Timeline
- 2026-08-31 Discovered during pass-2 audit of kern_jail.c (GLM 5.3); jailed-root host-clock step reproduced + fix validated same run.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-2800 Β· 7 files| File | Type | Description | Size | |
|---|---|---|---|---|
| jailsettime.c | β | 2.9 KB | view raw | |
| run.sh | β | 394 B | view raw | |
| run.log | β | 311 B | view raw | |
| run.patched.log | β | 388 B | view raw | |
| env.txt | β | 373 B | view raw | |
| fix.diff | β | 5.1 KB | view raw | |
| verdict.json | β | 3.7 KB | view raw |
Fix verification
fixedPatched kernel: jailed settimeofday returns EPERM and the host clock is unchanged (delta=0 vs -28s on stock). The exact error path (child jailed, jid assigned) is exercised in both runs.
findings/poc/DF-2800/run.patched.log; fix.diff (kern_jail.c leaf deny + kern_caps.c ocap forwarding)
Confirmed kernel references
Detail
Exploit chain
jailed uid-0 (the normal tenant model) -> settimeofday/clock_settime/adjtime/ntp_adjtime -> host-global clock manipulation. No memory-safety primitive; no uid0-on-host route.
Evidence (decisive lines)
['findings/poc/DF-2800/run.log β stock: settimeofday OK, host clock -28s, REPRODUCED line', 'findings/poc/DF-2800/run.patched.log β fixed: EPERM, NOT-REPRODUCED, delta=0, securelevel -1 (i.e. no securelevel protection even before fix)']
PoC changes
none essential β pack seed compiled and ran first try; libc has no jail(3) wrapper assumption removed by using syscall(SYS_jail=338) directly.
Verified recommended fix
prison_priv_check: SYSCAP_NOSETTIME -> EPERM, plus kern_caps.c must pass the ORIGINAL (un-rewritten) capability to prison_priv_check so per-leaf policy cannot be bypassed through the group-meta rewrite.
Verdict
REPRODUCED: uid 0 inside a completely ordinary jail (jail(2) v1, path /tmp, no IPs) successfully stepped the HOST system clock backward 30 seconds with settimeofday(2) (run.log: 'settimeofday: OK (stepped clock -30s)', host clock observed 1788266899 -> 1788266871 by the unjailed parent). Root cause is in prison_priv_check (kern_jail.c): SYSCAP_NOSETTIME is explicitly allowed in jails (:891-892), and every group-2 capability checked via caps_priv_check is blanket-allowed because kern_caps.c rewrites the cap to its group meta value before calling prison_priv_check β SYSCAP_NOSETTIME (0x28) becomes 2 == SYSCAP_SENSITIVEROOT, allowed at kern_jail.c:858-861. settime() only consults the global securelevel (kern_time.c:134-140); pr_securelevel is dead code. Same gate covers clock_settime, adjtime and ntp_adjtime. On the patched kernel (leaf case -> EPERM + kern_caps.c forwards the original cap) the identical PoC gets 'settimeofday: FAILED: Operation not permitted' with the host clock unchanged. Impact is a jail-isolation break (host-wide clock integrity): mapped to enum 'dos' because the reporting categories lack an integrity class β realistic damage is skew/step/freeze of the shared host clock degrading NTP/TLS/kerberos/cron/logging for the host and all co-resident jails.
No comments yet.