Bugtraq mailing list archives
Re: pt_chmod
From: peter () haywire DIALix COM (Peter Wemm)
Date: Mon, 5 Dec 1994 02:05:31 +0800 (WST)
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)
Current thread:
- Re: pt_chmod Bela Lubkin (Dec 02)
- Re: pt_chmod Karl Strickland (Dec 03)
- Re: pt_chmod Peter Wemm (Dec 03)
- Re: pt_chmod Peter Wemm (Dec 04)
- Re: pt_chmod Casper Dik (Dec 04)
- <Possible follow-ups>
- Re: pt_chmod Bela Lubkin (Dec 03)
- SCO (was Re: pt_chmod) Karl Strickland (Dec 04)
- Re: pt_chmod Bela Lubkin (Dec 04)
- Re: pt_chmod Peter Wemm (Dec 04)
- Re: pt_chmod Jeff Smith (Dec 04)