PPS_IOC_KCBIND missing privilege check allows unprivileged kernel-PLL binding (NTP confusion)
| Field | Value |
|---|---|
| ID | DF-0022 |
| Status | new |
| Severity | Low |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:L/A:N |
| CWE | CWE-862 Missing Authorization |
| File | sys/kern/kern_clock.c |
| Lines | 1680-1694 |
| Area | kern |
| Confidence | certain |
| Discovered | 2026-06-29 |
| Reported | pending |
Summary
The PPS_IOC_KCBIND handler in pps_ioctl() honors an unprivileged request
to bind the kernel hardpps() consumer (pps->kcmode = kapi->edge) without
any caps_priv_check_self(). The code carries a
/* XXX Only root should be able to do this */ comment acknowledging the
omission. The pps(4) cdev is created mode 0644, so any local user can open
/dev/pps0 and issue the ioctl. On a PPS_SYNC kernel, each subsequent PPS
pulse on that source drives hardpps(), poisoning the global pps_freq/
pps_jitter/pps_tf[]/pps_valid/STA_PPSSIGNAL state used by
ntp_update_second(). A confused-deputy result follows: a privileged ntpd
that enables STA_PPSFREQ/STA_PPSTIME will discipline the system clock
against attacker-influenced values (bounded by MAXFREQ/MAXPHASE).
Root cause
sys/kern/kern_clock.c:1680-1694:
case PPS_IOC_KCBIND:
#ifdef PPS_SYNC
kapi = (struct pps_kcbind_args *)data;
/* XXX Only root should be able to do this */
if (kapi->tsformat && kapi->tsformat != PPS_TSFMT_TSPEC)
return (EINVAL);
if (kapi->kernel_consumer != PPS_KC_HARDPPS)
return (EINVAL);
if (kapi->edge & ~pps->ppscap)
return (EINVAL);
pps->kcmode = kapi->edge; /* :1690 no privilege check */
return (0);
#else
return (EOPNOTSUPP);
#endif
The callers (sys/dev/misc/pps/pps.c:183, sys/dev/serial/sio/sio.c,
sys/bus/u4b/serial/usb_serial.c) forward the ioctl verbatim with no upstream
privilege check. pps(4) is created make_dev(..., UID_ROOT, GID_WHEEL, 0644, ...)
(sys/dev/misc/pps/pps.c:103-104). So any local user can open /dev/pps0 and
bind hardpps().
Threat model & preconditions
- Attacker position: any local unprivileged user who can open a PPS-capable
device node that is world-openable (the parallel-port
/dev/pps0is0644by default). - Privileges gained or impact: confused-deputy against NTP. The attacker
cannot directly step the clock, but can (a) poison the
pps_*quality counters (pps_jitcnt/pps_errcnt/pps_stbcnt/pps_calcnt) reported back tontpdviantp_adjtime(), and (b) if a privilegedntpdruns with PPS discipline (STA_PPSFREQand/orSTA_PPSTIME), steerpps_freq/time_offsetunder theMAXFREQ/MAXPHASEclamps โ bounded but arbitrary phase/frequency drift and the ability to spoofSTA_PPSSIGNAL. - Required config or capabilities: kernel built with
options PPS_SYNC(time-service/LINT64-derived kernels); a PPS event source the user can drive (e.g. parallel-port pin 10 viappbus); realized clock impact additionally needs a privileged PPS-disciplinedntpd. - Reachability:
ioctl(fd, PPS_IOC_KCBIND, ...)on an openable PPS device.
Proof of concept
PoC source: findings/poc/DF-0022/kcbind.c
Opens /dev/pps0 and issues PPS_IOC_KCBIND to demonstrate the privilege
bypass (the ioctl succeeds for an unprivileged user).
Build & run (unprivileged)
cc -o kcbind findings/poc/DF-0022/kcbind.c ./kcbind /dev/pps0
Expected output (bug present)
[+] KCBIND succeeded (privilege bypass) on /dev/pps0 as uid=1000
(Full clock-steering needs the additional preconditions above.) On a fixed
kernel the ioctl returns EPERM.
Impact
Low. A missing privilege check acknowledged in-tree by a XXX comment, with a
bounded confused-deputy effect against NTP (not direct clock control). The fix
is one check.
Recommended fix
Gate PPS_IOC_KCBIND behind the same privilege used for setting the time
(SYSCAP_NOSETTIME), resolving the long-standing XXX:
--- a/sys/kern/kern_clock.c
+++ b/sys/kern/kern_clock.c
@@ -1680,6 +1680,11 @@
case PPS_IOC_KCBIND:
#ifdef PPS_SYNC
+ /*
+ * Binding the kernel PPS consumer (hardpps) influences the
+ * system clock discipline, so require the set-time privilege.
+ */
+ if (caps_priv_check_self(SYSCAP_NOSETTIME))
+ return (EPERM);
kapi = (struct pps_kcbind_args *)data;
- /* XXX Only root should be able to do this */
if (kapi->tsformat && kapi->tsformat != PPS_TSFMT_TSPEC)
References
sys/kern/kern_clock.c:1680-1694โPPS_IOC_KCBIND(no privilege check;XXX).sys/dev/misc/pps/pps.c:103-104โpps(4)cdev created0644.sys/kern/kern_ntptime.cโhardpps()consumes the bound state.- CWE-862 Missing Authorization.
Timeline
- 2026-06-29 Discovered during automated file-by-file audit of
sys/kern/kern_clock.c. - pending Reported to DragonFlyBSD security contact.