kern/116181: /dev/io-related io access permissions are not propagated to every thread in a process

Kyle Larose kmlarose at csclub.uwaterloo.ca
Fri Sep 7 09:50:02 PDT 2007


>Number:         116181
>Category:       kern
>Synopsis:       /dev/io-related io access permissions are not propagated to every thread in a process
>Confidential:   no
>Severity:       non-critical
>Priority:       low
>Responsible:    freebsd-bugs
>State:          open
>Quarter:        
>Keywords:       
>Date-Required:
>Class:          sw-bug
>Submitter-Id:   current-users
>Arrival-Date:   Fri Sep 07 16:50:01 GMT 2007
>Closed-Date:
>Last-Modified:
>Originator:     Kyle Larose
>Release:        6.2-RELEASE-p3
>Organization:
Sandvine Inc.
>Environment:
FreeBSD bsd-build3.phaedrus.sandvine.com 6.2-RELEASE-p3 FreeBSD 6.2-RELEASE-p3 #
0: Tue Apr 17 22:03:22 EDT 2007     root at bsd-build3.phaedrus.sandvine.com:/usr/o
bj/usr/src/sys/bsd-build3  i386
>Description:
Some background:

Opening /dev/io is supposed to give permission to use instructions like inb/outb. If a process doesn't have this permission, it will fault on execution of those instructions. The permission is controlled throught the IOPL flag in the EEFLAGS register. On opening /dev/io, it sets the permission like so:

 64         td->td_frame->tf_eflags |= PSL_IOPL;

(from sys/i386/i386/io.c)

Now, when a process opens a file, its fd is supposed to be shared across every thread in the process. Consequently, one would think that the permissions associated with this fd would also be shared. However, this is not the case. 
It seems that only threads who are descendents of a thread that had /dev/io opened when they were created. Threads created by threads without the permissioin will not get the permission. Threads which exist at the time /dev/io is opened, other than the opening thread, will also not get the permission. So, a process will need to make sure that every thread needing IO permissions has an ancestor that opened /dev/io before the thread was created. This seems sub-optimal, and contrary to the spirit of sharing file descriptors over an entire process.

To make the problem worse, closing /dev/io works in the same manner:
 74         td->td_frame->tf_eflags &= ~PSL_IOPL;

This leads me to believe that every thread which inherited permissions from an ancestor thread will keep the permissions, despite the closure of /dev/io. In fact, since the fd is shared across the process, a thread without the permissions could close the file. I think that, for the same reason that the flag isn't propagated to all threads on opening /dev/io, it will in turn not be unset in all threads when closing /dev/io. So, we could have a process which doesn't have /dev/io open still have IO permissions. This would be a security hole.


I also looked at how the linux compat code handles iopl for i386, and it is similar to this (it sets some flags in td->frame), so the issue might also crop up there.
>How-To-Repeat:
I haven't tested the removal of permissions portion, but I have tested that permissions aren't shared across the processes.
I wrote a program that starts a thread, opens /dev/io in the main thread, then tries to write to the serial port in the second thread. It gives a bus error.

include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include <machine/cpufunc.h>
pthread_cond_t dev_cond;
pthread_mutex_t dev_lock;
pthread_t thr;
void io_main();
void do_write();
void *second_thread(void*);

int main(int argc, char ** argv)
{
    if(pthread_cond_init(&dev_cond, NULL))
    {
	printf("Error initializing condition var.\n");
	return 1;
    }
    if(pthread_mutex_init(&dev_lock, NULL))
    {
	printf("Error initializing mutex.\n");
	return 1;
    }
    io_main();
    return 0;
}

void do_write()
{
    outb(0x378, 'a');
    printf("done\n");
}

void io_main()
{ 
    int fd;
    pthread_mutex_lock(&dev_lock);
    if(pthread_create(&thr,NULL, second_thread, NULL))
    {
	printf("Error creating second thread\n");
	exit(1);
    }
    if((fd = open("/dev/io", O_RDWR)) == -1)
    {
	    printf("Error opening /dev/io: %i\n", fd);
	    exit(1);
    }
    pthread_cond_wait(&dev_cond,&dev_lock);
    close(fd);
}
void *second_thread(void *args)
{
    int fd;
    pthread_mutex_lock(&dev_lock);
    printf("Performing write in secondary...\n");
    do_write();
    pthread_mutex_unlock(&dev_lock);
    pthread_cond_signal(&dev_cond);
    return 0;
}

>Fix:


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


More information about the freebsd-bugs mailing list