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

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).

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)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-2800 Β· 7 files
FileTypeDescriptionSize
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

fixed
baseline reproduced→ patch + rebuild →patched clean

Patched 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)
↓ fix.diffDragonFly dfbsd 6.5-DEVELOPMENT DragonFly 6.5-DEVELOPMENT #1: Tue Sep 1 13:39:40 UTC 2026 root@dfbsd:/usr/obj/usr/src/sys/X86_64_GENERIC x86_64

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.