Debian bug report logs - #988
`script' is insecure, and general tty insecurity

Package: bsdutils; Reported by: iwj10@cus.cam.ac.uk (Ian Jackson); 139 days old.

Message received at debian-bugs:


From pixar.com!bruce Wed Jun 14 12:44:09 1995
Return-Path: <bruce@pixar.com>
Received: from pixar.com by mongo.pixar.com with smtp
	(Smail3.1.28.1 #15) id m0sLyMD-0007hOC; Wed, 14 Jun 95 12:44 PDT
Received: from mongo.pixar.com by pixar.com with SMTP id AA16863
  (5.67b/IDA-1.5 for debian-bugs-pipe@mongo.pixar.com); Wed, 14 Jun 1995 12:42:42 -0700
Received: by mongo.pixar.com (Smail3.1.28.1 #15)
	id m0sLyKr-00051OC; Wed, 14 Jun 95 12:42 PDT
Message-Id: <m0sLyKr-00051OC@mongo.pixar.com>
Date: Wed, 14 Jun 95 12:42 PDT
From: bruce@pixar.com (Bruce Perens)
To: debian-bugs@pixar.com, iwj10@cus.cam.ac.uk (Ian Jackson)
Subject: Re:  Bug#988: `script' is insecure, and general tty insecurity

Here is a get_pseudo_tty() function that attempts to jettision pernicious
listeners on the slave side. You can easily hack this to change the slave
to be owned by the real UID. I've also included an execute() function that
redirects input and output to the pseudo-tty. You can see how these are used
and also find a driver for doing asynchronous I/O using the select() system
call if you download the source for ax25-util. The calling sequence for
get_pseudo_tty() and execute() is:

int
main(int argc, char * * argv, char * * environment)
{
	int	masterFD;
	int slaveFD;
	static const char *	argumentVector = { "/bin/sh", 0 };

	masterFD = get_pseudo_tty(&slaveFD);

	if ( masterFD < 0 )
		complain_and_die();

	/*
	 * Start the client program  with input and output directed
	 * to the slave FD. Do I/O to that from the master FD.
	 */
	if ( !execute("/bin/sh", argumentVector, environment, slaveFD) )
		complain_and_die();

	...
}

	- Bruce


BEGIN pseudo_tty.c

/* AX.25 Utilities: Attach an interface.
 * Bruce Perens, November 1994
 *
 * Copyright 1994 Bruce Perens.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 */
#include <stdlib.h>
#include <unistd.h>
#include <termios.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>

#define	NAME_SIZE	64

static const char	Prototype[] = "/dev/pty";
#define	PROTOTYPE_BASE	5 /* index to "pty" in prototype. */

static int
open_pseudo_tty(const char * name, int * slave)
{
	char		slaveName[NAME_SIZE];
	int		master = open(name, O_RDWR, 0);
	struct termios	t;

	if ( master < 0 )
		return -1;

	strcpy(slaveName, name);
	
	slaveName[PROTOTYPE_BASE] = 't';

	/* Close master again to jettison any pernicious listeners on slave
	 * side. I'd like to be able to lock opens on the slave side while
	 * this is going on. This won't work if you're not root.
	 */
	chown(slaveName, 0, 0);
	chmod(slaveName, 0600);
	close(master);

	/*
	 * Closing the master hung up on any listeners on the slave side. They
	 * can't open it again unless they are root.
 	 */
	if ( (master = open(name, O_RDWR, 0)) < 0 )
		return -1;

	if ( (*slave = open(slaveName, O_RDWR, 0)) < 0 ) {
		close(master);
		return -1;
	}

	if ( tcgetattr(*slave, &t) == 0 ) {
		/*
		 * Attempt to provide a consistent environment upon open.
		 * Of course if you are running a script you can override
		 * this by running stty.
		 */
		t.c_cc[VINTR]	= 'c' & 0x1f;
		t.c_cc[VQUIT]	= '\\' & 0x1f;
		t.c_cc[VERASE]	= 'h' & 0x1f;
		t.c_cc[VKILL]	= 'u' & 0x1f;
		t.c_cc[VEOF]	= 'd' & 0x1f;
		t.c_cc[VEOL]	= '\n';
		t.c_cc[VSTOP]	= 's' & 0x1f;
		t.c_cc[VSTART]	= 'q' & 0x1f;
		t.c_cc[VSUSP]	= 'z' & 0x1f;
		t.c_cc[VLNEXT]	= 'v' & 0x1f;
		t.c_cc[VWERASE]	= 'w' & 0x1f;
		t.c_cc[VREPRINT]= 'r' & 0x1f;
		t.c_cc[VDISCARD]= 'o' & 0x1f;
		t.c_iflag = BRKINT|ICRNL;
		t.c_oflag = OPOST;
		t.c_cflag = B9600|CS8|CREAD|HUPCL;
		t.c_lflag = ISIG|ICANON|ECHO|ECHOE;
		t.c_line = 0;
		tcsetattr(*slave, TCSANOW, &t);
	}

	return master;
}

int
get_pseudo_tty(int * slave)
{
	char			name[NAME_SIZE];
	char * const		ones = &name[sizeof(Prototype)];
	char * const		tens = &name[sizeof(Prototype) - 1];

	strcpy(name, Prototype);
	name[sizeof(Prototype) + 1] = '\0';

	for ( *tens = 'p'; *tens <= 's'; ++*tens ) {
		int	n;
		for ( n = 0; n < 16; n++ ) {
			static const char Hexits[16] = "0123456789abcdef";
			int	master;

			*ones = Hexits[n];
			master = open_pseudo_tty(name, slave);

			if ( master >= 0 )
				return master;
		}
	}
	return -1;
}

BEGIN execute.c

/* AX.25 Utilities: Run a program, with input and output directed
 *	to a file descriptor.
 *
 * 
 * Bruce Perens, November 1994
 *
 * Copyright 1994 Bruce Perens.
 *
 *  This program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2 of the License, or
 *  (at your option) any later version.
 *
 *  This program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *  GNU General Public License for more details.
 */
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>

int
execute(
 const char *	file
,char * const *	argv
,char * const *	envp
,int		ioFile)
{
	int	pid;

	signal(SIGCLD, SIG_IGN);	/* No zombies */

	pid = fork();

	if ( pid < 0 )
		return -1;
	else if ( pid == 0 ) {
		/* In the child process */

		int	max = (int)sysconf(_SC_OPEN_MAX);
		int	fd;
		int	n;

		for ( fd = 0; fd < max; fd++ ) {
			if ( fd != ioFile )
				close(fd);
		}
		/*
		 * If the IO file isn't 0, 1, or 2, I'm starting a new
		 * session on some other device.
		 */
		if ( ioFile > 2 )
			setsid();

		for ( fd = 0; fd <= 2; fd++ ) {
			if ( fd != ioFile && dup2(ioFile, fd) != fd )
				_exit(-1);
		}

		if ( ioFile > 2 )
			close(ioFile);

		/*
		 * Try to give the process as pristine an environment
		 * as possible.
		 */
		for ( n = 0; n < NSIG; n++ )
			signal(n, SIG_DFL);

		/*
		 * If I started a new session above, set the tty process
		 * group to match it.
		 */
		if ( ioFile > 2 )
			tcsetpgrp(0, getpgrp());

		execve(file, argv, envp);
		_exit(-1);
	}
	return pid;
}
--
-- Attention Ham Radio Operators: For information on "Linux for Hams", read
-- the World Wide Web page http://www.hams.com/perens/LinuxForHams, or send
-- an e-mail message containing the word "help" to info@hams.com .

Acknowledgement sent to bruce@pixar.com (Bruce Perens):
Extra info received and forwarded. Full text available.
Information forwarded to debian-devel@pixar.com:
Bug#988; Package bsdutils. Full text available.

Message received at debian-bugs:


From cus.cam.ac.uk!iwj10 Wed Jun 14 05:54:23 1995
Return-Path: <iwj10@cus.cam.ac.uk>
Received: from pixar.com by mongo.pixar.com with smtp
	(Smail3.1.28.1 #15) id m0sLrxe-0007mTC; Wed, 14 Jun 95 05:54 PDT
Received: from bootes.cus.cam.ac.uk by pixar.com with SMTP id AA25300
  (5.67b/IDA-1.5 for debian-bugs-pipe@mongo.pixar.com); Wed, 14 Jun 1995 05:52:52 -0700
Received: by bootes.cus.cam.ac.uk 
	(Smail-3.1.29.0 #36) id m0sLrxB-000C01C; Wed, 14 Jun 95 13:53 BST
Received: by chiark
	id <m0sLrhb-0000XRZ@chiark.al.cl.cam.ac.uk>
	(Debian /\oo/\ Smail3.1.29.1 #29.32); Wed, 14 Jun 95 13:37 BST
Message-Id: <m0sLrhb-0000XRZ@chiark.al.cl.cam.ac.uk>
Date: Wed, 14 Jun 95 13:37 BST
From: iwj10@cus.cam.ac.uk (Ian Jackson)
To: Debian bugs submission address <debian-bugs@pixar.com>
Subject: `script' is insecure, and general tty insecurity

Package: bsdutils
Version: 1.2-1

chiark:~> tty
/dev/ttyp3
chiark:~> script
Script started, output file is typescript
chiark:~> tty
/dev/ttyp7
chiark:~> ls -al /dev/ttyp3 /dev/ttyp7
crw--w--w-   1 ian      ian        4, 195 Jun 14 13:31 /dev/ttyp3
crw-rw-rw-   1 root     root       4, 199 Jun 14 13:31 /dev/ttyp7
chiark:~> exit
exit
Script done, output file is typescript
chiark:~> ls -al /dev/ttyp3 /dev/ttyp7
crw--w--w-   1 ian      ian        4, 195 Jun 14 13:31 /dev/ttyp3
crw-rw-rw-   1 root     root       4, 199 Jun 14 13:31 /dev/ttyp7
chiark:~> 

Clearly /dev/ttyp7 should, while script is running:

* not be readable by everyone
* be owned by the user (so that they can use mesg and biff)
* have mesg off by default

Fixing this will require the intervention of a setuid root program
(either script will have to be setuid or another program will have to
be made).

There may be other security problems, notably races in the pty
allocation.

In general this is a very messy area, and the solutions to the
problems here are likely to involve nontrivial amounts of thought,
coding and/or introduction of additional software.  This problem with
programs like `script' is common on many unices, but we should arrange
to find solutions at least for programs we supply.

There are other problems related to having globally-writeable tty's.
IMO tty's should be made group-writeable only by a special group
(conventially called `tty'), to which all programs like `write' and
`talk' will have to be setgid.  This is probably a major undertaking,
though, requiring changes to login, telnet, &c &c

Ian.

Acknowledgement sent to iwj10@cus.cam.ac.uk (Ian Jackson):
New bug report received and forwarded. Full text available.
Report forwarded to debian-devel@pixar.com:
Bug#988; Package bsdutils. Full text available.
Ian Jackson / iwj10@thor.cam.ac.uk, with the debian-bugs tracking mechanism
This page last modified 07:43:01 GMT Wed 01 Nov