/*
 * arc4samp.c - DragonFly kernel module (KLD) sampling the deployed
 * sys/libkern/arc4random.c generator from inside the running kernel.
 *
 * Evidence class: "KLD in-kernel sampling" for the pass-2 audit of
 * sys/libkern/arc4random.c.
 *
 * Devices:
 *   /dev/arc4s32  - stream of karc4random() words (LE)
 *   /dev/arc4sbuf - stream of karc4random_buf() bytes
 *   /dev/arc4s64  - stream of karc4random64() words (LE)
 *   /dev/arc4srep - one-shot text report: 2^20 karc4random64 draws,
 *                   counting draws whose upper 32 bits are 0xffffffff
 *                   and draws whose 4th byte (bits 24..31) is >= 0x80.
 *                   (sign-extension bug PoC; predicted ~50% / ~50%)
 *
 * Build: make  (needs kernel source at /usr/src)
 * Load:  kldload ./arc4samp.ko
 */
#include <sys/param.h>
#include <sys/kernel.h>
#include <sys/systm.h>
#include <sys/module.h>
#include <sys/conf.h>
#include <sys/devfs.h>
#include <sys/uio.h>
#include <sys/malloc.h>
#include <sys/libkern.h>
#include <sys/types.h>

static d_open_t		arc4samp_open;
static d_close_t	arc4samp_close;
static d_read_t		arc4samp_read;

static struct dev_ops arc4samp_cdevsw = {
	{ "arc4samp", 0, 0 },
	.d_open		= arc4samp_open,
	.d_close	= arc4samp_close,
	.d_read		= arc4samp_read,
};

static int
arc4samp_open(struct dev_open_args *ap)
{
	/* fresh one-shot report per open */
	ap->a_head.a_dev->si_drv1 = NULL;
	return 0;
}

static int
arc4samp_close(struct dev_close_args *ap)
{
	return 0;
}

static int
arc4samp_read(struct dev_read_args *ap)
{
	struct cdev *dev = ap->a_head.a_dev;
	struct uio *uio = ap->a_uio;
	uint8_t *buf;
	int error = 0;
	int unit = dev2unit(dev);

	buf = kmalloc(65536, M_TEMP, M_WAITOK);
	if (buf == NULL)
		return ENOMEM;

	while (uio->uio_resid > 0 && error == 0) {
		int n = (int)szmin(uio->uio_resid, 65536);

		switch (unit) {
		case 0: {		/* karc4random words */
			int off = 0;
			while (off + 4 <= n) {
				uint32_t v = karc4random();
				memcpy(buf + off, &v, 4);
				off += 4;
			}
			break;
		}
		case 1:			/* karc4random_buf stream */
			karc4random_buf(buf, n);
			break;
		case 2: {		/* karc4random64 words */
			int off = 0;
			while (off + 8 <= n) {
				uint64_t v = karc4random64();
				memcpy(buf + off, &v, 8);
				off += 8;
			}
			break;
		}
		case 3: {		/* one-shot bias report (per open) */
			const int NDRAWS = 1 << 20;
			uint64_t top32ones = 0, b3high = 0;
			int len;

			if (dev->si_drv1)
				goto out;	/* EOF after first report */
			dev->si_drv1 = (void *)1;
			for (int k = 0; k < NDRAWS; k++) {
				uint64_t v = karc4random64();
				if (((v >> 32) & 0xffffffffULL) == 0xffffffffULL)
					top32ones++;
				if (((v >> 24) & 0xff) >= 0x80)
					b3high++;
			}
			len = ksnprintf((char *)buf, 65536,
			    "karc4random64 draws=%d\n"
			    "top32==0xffffffff: %ju (%d.%04d; bug predicts ~0.5000)\n"
			    "byte3>=0x80:       %ju (%d.%04d)\n",
			    NDRAWS,
			    (uintmax_t)top32ones,
			    (int)(top32ones * 10000 / NDRAWS / 10000),
			    (int)(top32ones * 10000 / NDRAWS % 10000),
			    (uintmax_t)b3high,
			    (int)(b3high * 10000 / NDRAWS / 10000),
			    (int)(b3high * 10000 / NDRAWS % 10000));
			error = uiomove(buf, len, uio);
			goto out;
		}
		default:
			error = ENXIO;
			goto out;
		}
		error = uiomove(buf, n, uio);
	}
out:
	kfree(buf, M_TEMP);
	return error;
}

static int
arc4samp_modevent(module_t mod, int type, void *data)
{
	switch (type) {
	case MOD_LOAD:
		make_dev(&arc4samp_cdevsw, 0, UID_ROOT, GID_WHEEL, 0444,
		    "arc4s32");
		make_dev(&arc4samp_cdevsw, 1, UID_ROOT, GID_WHEEL, 0444,
		    "arc4sbuf");
		make_dev(&arc4samp_cdevsw, 2, UID_ROOT, GID_WHEEL, 0444,
		    "arc4s64");
		make_dev(&arc4samp_cdevsw, 3, UID_ROOT, GID_WHEEL, 0444,
		    "arc4srep");
		kprintf("arc4samp: loaded\n");
		break;
	case MOD_UNLOAD:
		destroy_dev(devfs_find_device_by_name("arc4s32"));
		destroy_dev(devfs_find_device_by_name("arc4sbuf"));
		destroy_dev(devfs_find_device_by_name("arc4s64"));
		destroy_dev(devfs_find_device_by_name("arc4srep"));
		kprintf("arc4samp: unloaded\n");
		break;
	default:
		return EOPNOTSUPP;
	}
	return 0;
}

DEV_MODULE(arc4samp, arc4samp_modevent, NULL);
