xorl %eax, %eax

CVE-2008-5006: University of Washington IMAP c-client NULL Pointer

leave a comment »

This vulnerability was disclosed by Nico Golde on oss-security mailing list where he noticed that the diff file between the new release and the older one was including a fix for a NULL pointer dereference that was silently being fixed. Here is the buggy code which can be found at src/c-client/smtp.c like that:

/* Mail Transfer Protocol close connection
 * Accepts: SEND stream
 * Returns: NIL always
 */

SENDSTREAM *smtp_close (SENDSTREAM *stream)
{
  if (stream) {			/* send "QUIT" */
    if (stream->netstream) {	/* do close actions if have netstream */
      smtp_send (stream,"QUIT",NIL);
      net_close (stream->netstream);
    }
				/* clean up */
    if (stream->host) fs_give ((void **) &stream->host);
    if (stream->reply) fs_give ((void **) &stream->reply);
    if (ESMTP.dsn.envid) fs_give ((void **) &ESMTP.dsn.envid);
    if (ESMTP.atrn.domains) fs_give ((void **) &ESMTP.atrn.domains);
    fs_give ((void **) &stream);/* flush the stream */
  }
  return NIL;
}

As you can read from the comments, this routine is used to close an SMTP connection. If we have a quick look at src/c-client/mail.h we could see how ‘SENDSTREAM’ data type is defined.

/* Mail delivery I/O stream */

typedef struct send_stream {
  NETSTREAM *netstream;		/* network I/O stream */
  char *host;			/* SMTP service host */
  char *reply;			/* last reply string */
     ...
      } ext;
    } nntp;
  } protocol;
} SENDSTREAM;

Basically we’re dealing with just some pointers. Back to smtp_close() we can see that it checks if the passed stream is non-NULL which means that it is present, and if this is the case and network I/O stream is also present too (the second if clause), it will just transmit a “QUIT” message and attempt to close the network I/O stream using net_close().
However, during the execution of smtp_send() the network I/O stream could have been closed. Let’s have a look at smtp_send() which is located in the same C code file…

/* Simple Mail Transfer Protocol send command
 * Accepts: SEND stream
 *	    text
 * Returns: reply code
 */

long smtp_send (SENDSTREAM *stream,char *command,char *args)
{
  long ret;
  char *s = (char *) fs_get (strlen (command) + (args ? strlen (args) + 1 : 0)
			     + 3);
				/* build the complete command */
  if (args) sprintf (s,"%s %s",command,args);
  else strcpy (s,command);
  if (stream->debug) mail_dlog (s,stream->sensitive);
  strcat (s,"\015\012");
				/* send the command */
  if (stream->netstream && net_soutr (stream->netstream,s)) {
    do stream->replycode = smtp_reply (stream);
    while ((stream->replycode < 100) || (stream->reply[3] == '-'));
    ret = stream->replycode;
  }
  else ret = smtp_fake (stream,"SMTP connection broken (command)");
  fs_give ((void **) &s);
  return ret;
}

As you can read, this code builds the command to be executed, in this case QUIT, and then checks if network I/O stream is present and also that sending the NULL terminated string ‘s’ to ‘stream->netstream’ succeeds since net_soutr() is a simple routine from src/c-client/mail.c that does just this.

/* Network send null-terminated string
 * Accepts: Network stream
 *	    string pointer
 * Returns: T if success else NIL
 */

long net_soutr (NETSTREAM *stream,char *string)
{
  return (*stream->dtb->soutr) (stream->stream,string);
}

However, if either the network I/O stream is closed (which means that ‘stream->netstream’ will be NULL) or if the transmission using net_soutr() fails, it will fall to the ‘else’ clause. Here, smtp_fake() will be invoked which will close the network I/O stream as you can read below.

/* Simple Mail Transfer Protocol set fake error and abort
 * Accepts: SMTP stream
 *	    error text
 * Returns: SMTPSOFTFATAL, always
 */

long smtp_fake (SENDSTREAM *stream,char *text)
{
  if (stream->netstream) {	/* close net connection if still open */
    net_close (stream->netstream);
    stream->netstream = NIL;
  }
				/* set last error */
  return smtp_seterror (stream,SMTPSOFTFATAL,text);
}

After closing the network I/O stream it sets its pointer to NIL. If we made it to this code path, then smtp_close() will result in a NULL pointer dereference when it’ll attempt to re-close the network I/O stream. Specifically, net_close() will attempt to execute a callback function on the pointer passed to it as an argument like that:

/* Network close
 * Accepts: Network stream
 */

void net_close (NETSTREAM *stream)
{
  if (stream->stream) (*stream->dtb->close) (stream->stream);
  fs_give ((void **) &stream);
}

To fix this, smtp_close() was updated to check if network I/O stream is NULL before proceeding and closing it.

     if (stream->netstream) {   /* do close actions if have netstream */
       smtp_send (stream,"QUIT",NIL);
-      net_close (stream->netstream);
+      if (stream->netstream)   /* could have been closed during "QUIT" */
+        net_close (stream->netstream);
     }

Really interesting vulnerability. If you could somehow get a mapping on NULL remotely with just a controlled address in the offset of ‘stream->dtb->close’ it could result in remote code execution…

Written by xorl

January 2, 2010 at 15:34

Posted in bugs

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s