[345] in bugtraq

home help back first fref pref prev next nref lref last post

Re: pt_chmod

daemon@ATHENA.MIT.EDU (Peter Wemm)
Sun Dec 4 14:29:50 1994

From: Peter Wemm <peter@haywire.DIALix.COM>
To: rwing!pat@ole.cdac.com (Pat Myrto)
Date: Mon, 5 Dec 1994 02:05:31 +0800 (WST)
Cc: bugtraq@fc.net
In-Reply-To: <9412041332.AA06522@rwing.UUCP> from "Pat Myrto" at Dec 4, 94 05:32:06 am

Pat Myrto wrote to me in a private email message:
(My apologies, Pat.  I hope the snippits I've included are OK..)

> "In the previous message, Peter Wemm said..."
> > 
> > [ ...]
> >
> > Fortunately for SVR4 (and probably solaris) systems, the filesystem
> > always returns an "ENOENT" if the user trys to create a file with zero
> > length, so the "chown("", uid, gid)" should never succeed.  However, on
>   ^^^^^
> I take it that you meant 'zero name length' instead of 'zero length', right?
[...]

Yep.  for example: (and /etc/termcap exists...)

$ truss ln -s /etc/termcap ""

execve("/usr/bin/ln", 0x08047CD8, 0x08047CEC)  argc = 4
open("/dev/zero", O_RDONLY, 020000527740)	= 3
mmap(0x00000000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x8002D000
getuid()					= 433  [ 433 ]
getuid()					= 433  [ 433 ]
getgid()					= 304  [ 304 ]
getgid()					= 304  [ 304 ]
close(3)					= 0
sysi86(SI86FPHW, 0x8002B6D4, 0x8002AFE0, 0x8000B301) = 0x00000000
xstat(2, "", 0x0804B8F0)			Err#2  ENOENT
symlink("/etc/termcap", "")			Err#2  ENOENT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
write(2, " l n :   c a n n", 8)			= 8
write(2, " o t   c r e a t e  ", 10)		= 10
write(2, 0x8002A50C, 0)				= 0
write(2, "\n", 1)				= 1
write(2, " l n", 2)				= 2
write(2, " :  ", 2)				= 2
write(2, " N o   s u c h   f i l e".., 25)	= 25
write(2, "\n", 1)				= 1
_exit(2)

and:
$ truss dd of=""
execve("/usr/bin/dd", 0x08047CF0, 0x08047CFC)  argc = 2
open("/dev/zero", O_RDONLY, 020000527740)	= 3
mmap(0x00000000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE, 3, 0) = 0x8002D000
getuid()					= 433  [ 433 ]
getuid()					= 433  [ 433 ]
getgid()					= 304  [ 304 ]
getgid()					= 304  [ 304 ]
close(3)					= 0
sysi86(SI86FPHW, 0x8002B6D4, 0x8002AFE0, 0x8000B301) = 0x00000000
dup(0)						= 3
creat("", 0666)					Err#2  ENOENT
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
write(2, " d d :   c a n n o t   c".., 18)	= 18
write(2, 0x8002A50C, 0)				= 0
write(2, " :   N o   s u c", 8)			= 8
write(2, " h   f i l e   o r   d i".., 19)	= 19
write(2, 0x8002A50C, 0)				= 0
write(2, "\n", 1)				= 1
_exit(2)

This is just as well, because the SVR4 pt_chmod (note: *not* the
solaris one) attempts to give you ownership of a file called "".

Pat also commented:
> Thats a trap with small SUID programs:  Folks could easily tend to think
> a simple one or two line command is so simple that a thorough test for
> a bug is not made like is done on something a bit larger.

"There is no such thing as a program without a bug"

Likewise, the first version definately doesn't work... At all... Serves
me right for building it, testing it, and including it in the email,
then editing it while in email form to use fchown() and fchmod() which
I thought was a great idea at the time...  So much for that last shred
of credibility... :-(

The second version does work (well, at least, it worked on my
machine..) as this truss from screen-3.5.2 shows.  Note that I had to
trace it as root so that it would cross the exec() of pt_chmod. Also,
I did actually test it as a mortal user this time too. :-)

10929:	open("/dev/ptmx", O_RDWR, 0)			= 6
----begin ptsname()
10929:	ioctl(6, I_STR, 0x080461D0)			= 0
10929:		cmd=(('P'<<8)|1) timout=0 len=0 dp=0x00000000
10929:	fxstat(2, 6, 0x080461E0)			= 0
10929:	    d=0x00000001 i=58880 m=0020000 l=0  u=0     g=0     rdev=0x002C0000
10929:		at = Dec  5 01:10:09 WST 1994  [ 786561009 ]
10929:		mt = Dec  5 01:10:09 WST 1994  [ 786561009 ]
10929:		ct = Dec  5 01:10:09 WST 1994  [ 786561009 ]
10929:	    bsz=8192  blks=0     fs=ufs2
10929:	access("/dev/pts/0", 0)				= 0
----end ptsname()
----begin unlockpt()
10929:	ioctl(6, I_STR, 0x08046264)			= 0
10929:		cmd=(('P'<<8)|2) timout=0 len=0 dp=0x00000000
----end unlockpt()
----begin grantpt()
10929:	fork()						= 10970
10970:	fork()		(returning as child ...)	= 10929
10970:	execve("/usr/lib/pt_chmod", 0x08046250, 0x08047CBC)  argc = 2
10970:	 argv: pt_chmod 6
10970:	open("/dev/zero", O_RDONLY, 020000527740)	= 7
10970:	mmap(0x00000000, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE, 7, 0) = 0x8002D000
10970:	getuid()					= 0  [ 0 ]
10970:	getuid()					= 0  [ 0 ]
10970:	getgid()					= 0  [ 0 ]
10970:	getgid()					= 0  [ 0 ]
10970:	close(7)					= 0
10970:	sysi86(SI86FPHW, 0x8002B6D4, 0x8002AFE0, 0x8000B301) = 0x00000000
10970:	getgid()					= 0  [ 0 ]
10970:	getuid()					= 0  [ 0 ]
10970:	open("/etc/group", O_RDONLY, 020000527740)	= 7
10970:	fxstat(2, 7, 0x08047BB4)			= 0
10970:	    d=0x00000001 i=1558  m=0100644 l=1  u=0     g=3     sz=555
10970:		at = Dec  5 01:10:00 WST 1994  [ 786561000 ]
10970:		mt = Nov  3 05:55:58 WST 1994  [ 783813358 ]
10970:		ct = Nov  3 05:55:58 WST 1994  [ 783813358 ]
10970:	    bsz=1024  blks=2     fs=ufs2
10970:	brk(0x0804A69C)					= 0
10970:	read(7, " r o o t : ! : 0 : r o o".., 555)	= 555
10970:	close(7)					= 0
-------begin call to ptsname()
10970:	ioctl(6, I_STR, 0x08047BC4)			= 0
10970:		cmd=(('P'<<8)|1) timout=0 len=0 dp=0x00000000
^^^^^^ this is ioctl ISPTM - which securely tests to see if the fd is
                                         in fact a clone of the pty master.
10970:	fxstat(2, 6, 0x08047BD4)			= 0
10970:	    d=0x00000001 i=58880 m=0020000 l=0  u=0     g=0     rdev=0x002C0000
10970:		at = Dec  5 01:10:09 WST 1994  [ 786561009 ]
10970:		mt = Dec  5 01:10:09 WST 1994  [ 786561009 ]
10970:		ct = Dec  5 01:10:09 WST 1994  [ 786561009 ]
10970:	    bsz=8192  blks=0     fs=ufs2
10970:	access("/dev/pts/0", 0)				= 0
--------end call to ptsname()
10970:	chmod("/dev/pts/0", 0620)			= 0
10970:	chown("/dev/pts/0", 0, 7)			= 0
10970:	_exit(0)
10929:	    Received signal #18, SIGCLD, in waitsys() [default]
10929:	      siginfo: SIGCLD CLD_EXITED pid=10970 uid=1 status=0x0000
10929:	waitsys(0x00000000, 10970, 0x080461C0, WEXITED|WTRAPPED) = 0
10929:		status=0x0012
----end grantpt()

FYI: for reference, from <sys/ptms.h>
---------------------------------
/*
 * ioctl commands
 */
#define ISPTM	(('P'<<8)|1)	/* query for master */
#define UNLKPT	(('P'<<8)|2)	/* unlock master/slave pair */
---------------------------------

Interestingly though, I think the design of the ptmx and pts system is
in general superior to the traditional pty design.

There are no real race conditions..  This is how it works:

0: All slave pty's are locked in the idle state while there is no fd
attached to the corresponding master.

1: process opens /dev/ptmx, which is a clone device, ie: you get
allocated a unique minor device number within the kernel.

2: process calls grantpt(), on the fd from /dev/ptms which calls
/usr/lib/pt_chmod to securely (HA!!!) chown the corresponding slave
device from owner root, group root, mode 666 to your userid, and
chmods it to stop others from grabbing it.

3: process calls unlockpt(), again, on the /dev/ptmx file descriptor,
which releases the lock on the slave device.

4: process now opens a fd on /dev/pts/xxxx, where xxxx is the
corresponding minor number for the initial open of /dev/ptmx.

later: when the fd on the master and slave are closed, the lock is
reset. If another process opens /dev/ptmx, it wont get a descriptor
corresponding to anything that has anything silently attached to it. 

The good part is that your userid has ownership of the slave before
*anything* can open it.  If correctly programmed, there can be no race
conditions... 

Now, since this is a bug disclosure list, here's a quickie: Can
anybody see the security bug in the above code from screen-3.5.2?  It
does an unlockpt() *before* calling grantpt().  That means somebody
could leave a process sitting in a spin loop attempting to open() the
next free slave pty, and if they win the race, they will have an open
file desciptor on somebody else's screen session.  (by god, I hope I'm
not crying "wolf" prematurely again... :-)

Here's the bit of code in question from screen-3.5.2:

  if ((f = open("/dev/ptmx", O_RDWR)) == -1)
    return -1;
       
  if ((m = ptsname(f)) == NULL || unlockpt(f) || grantpt(f))
                                  ^^^^^^^^^^^    ^^^^^^^^^^
    {
      signal(SIGCHLD, sigcld);
      close(f);
      return -1;
    } 

Note that it's allowing access to the slave *before* claming ownership.

If you use screen on an account that has any privileges at all, you'd
best change that line to:

  if ((m = ptsname(f)) == NULL || grantpt(f) || unlockpt(f))

I wonder if this bug is also in any of in.telnetd/in.rlogind etc?  Hmm.
No.. they appear to do it correctly..  as does the telnetd in
telnet.94.02.07.  the 'script' command appears safe too.

However, telnet.94.02.07 has another bug in it's telnetd.  It doesn't
restore the ownership of the slave pty back to root.root and mode 0666.
This will cause grantpt() to fail if called by an unprivileged process.

-Peter (with crossed fingers)


home help back first fref pref prev next nref lref last post