Subject: Patch for 8lgm syslog/sendmail vulnerability, 4.4lite machines
To: None <bugtraq@crimelab.com>
From: Perry E. Metzger <perry@piermont.com>
List: current-users
Date: 08/29/1995 05:09:15
[I've bcc'ed a bunch of people on this -- don't be suprised if you got
it unsolicited.]

Summary: This message contains a patch that is strongly believed by me
to fix the very nasty security vulnerability in most systems described
a few hours ago by the 8lgm people in the syslog client code -- see
[8lgm]-Advisory-22.UNIX.syslog.2-Aug-1995. The patch works on 4.4lite
BSD derived systems but should be easily modified to work on other
systems.

[You can go to http://www.8lgm.org/ to get a copy of the 8lgm report.]

This vulnerability is one that allows people to break in to a machine
via sendmail and apparently, from a cursory glance, several other
daemons -- it also appears to allow people to break in to machines
running sendmail through most kinds of firewalls.

Warning: I have not been able to test the patch against every possible
variation on this particular bug. Caveat emptor! I'm releasing it now
because it seemed too important to delay.

The patch isn't as elegant as it could be but I wrote it in the middle
of the night -- please forgive the excessive tests and the like as I
was trying to be conservative and I was tired.

This is a context diff to syslog.c in NetBSD-current's libc, which is
essentially the same as the syslog.c in any other 4.4lite based
system, such as BSDI, FreeBSD, etc. The patch prevents any program
calling syslog(3) from overflowing its buffer, and thus prevents all
sorts of nasty fandango-on-stack based evils. The 8lgm advisory
suggested that programs could check the length of their parameters to
syslog themselves but I could find no reasonable way to do that given
that parameters like "%m" result in unpredictable length expansions.

The patch should also work on other operating systems provided you 1)
are running a 4.4 compatible syslog, 2) get a whole copy of
libc/gen/syslog.c from your friendly neighborhood copy of 4.4lite or a
descendant, and 3) have snprintf on your system or can find a version
of snprintf that will run on your system. I have not tested it on
machines other than 4.4lite based boxes, however. If you have
something else, you are on your own.

Unfortunately, there is no obvious way to fix this problem without
snprintf. If you are on a Sun or similar platform, you'd better find a
copy of an snprintf from somewhere.

The following short stretch of code, produced by John Hawkinson, (and
distributed without his formal permission; I doubt he'll mind, though)
will test whether your machine has what I believe to be the problem
documented by 8lgm. It should coredump or something similar on
machines with the problem -- it should work just fine (albeit with a
truncated log message) on machines that have been patched. Following
the test program is the patch. Note that just because this program
works doesn't mean you aren't necessarily vulnerable!

--cut here--------------------------------------------------------------------
/* Test program by jhawk@mit.edu -- comment by perry@piermont.com */
#include <syslog.h>

void Amain () {
   char buffer[4096];
   int i;

  for (i=0; i<4000; i++)
	buffer[i]=65;

  syslog(LOG_ERR, buffer);

}

void main() {
  Amain();
}
--cut here--------------------------------------------------------------------

And here is the patch.

--cut here--------------------------------------------------------------------
*** syslog.c.orig	Tue Apr 11 05:55:22 1995
--- syslog.c	Tue Aug 29 04:11:25 1995
***************
*** 105,109 ****
  	time_t now;
  	int fd, saved_errno;
! 	char *stdp, tbuf[2048], fmt_cpy[1024];
  
  #define	INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
--- 105,119 ----
  	time_t now;
  	int fd, saved_errno;
! 
! #define TBUFLEN 2048
! #define FMT_CPYLEN 1024
! 
! 	int tbuf_len, fmt_cpy_len;
! 	int prlen;
! 	char *stdp, tbuf[TBUFLEN], fmt_cpy[FMT_CPYLEN];
! 
! 	tbuf_len = TBUFLEN;
! 	fmt_cpy_len = FMT_CPYLEN;
! 
  
  #define	INTERNALLOG	LOG_ERR|LOG_CONS|LOG_PERROR|LOG_PID
***************
*** 127,144 ****
  	/* Build the message. */
  	(void)time(&now);
! 	p = tbuf + sprintf(tbuf, "<%d>", pri);
! 	p += strftime(p, sizeof (tbuf) - (p - tbuf), "%h %e %T ",
  	    localtime(&now));
  	if (LogStat & LOG_PERROR)
  		stdp = p;
  	if (LogTag == NULL)
  		LogTag = __progname;
- 	if (LogTag != NULL)
- 		p += sprintf(p, "%s", LogTag);
- 	if (LogStat & LOG_PID)
- 		p += sprintf(p, "[%d]", getpid());
  	if (LogTag != NULL) {
  		*p++ = ':';
  		*p++ = ' ';
  	}
  
--- 137,176 ----
  	/* Build the message. */
  	(void)time(&now);
! 	prlen = snprintf(tbuf, tbuf_len, "<%d>", pri);
! 	tbuf_len -= prlen;
! 	if (tbuf_len < 0)
! 	  tbuf_len = 0;
! 	p = tbuf + prlen;
! 	prlen = strftime(p, tbuf_len, "%h %e %T ",
  	    localtime(&now));
+ 	tbuf_len -= prlen;
+ 	if (tbuf_len < 0)
+ 	  tbuf_len = 0;
+ 	p += prlen;
  	if (LogStat & LOG_PERROR)
  		stdp = p;
  	if (LogTag == NULL)
  		LogTag = __progname;
  	if (LogTag != NULL) {
+ 		prlen = snprintf(p, tbuf_len, "%s", LogTag);
+ 		tbuf_len -= prlen;
+ 		if (tbuf_len < 0)
+ 		  tbuf_len = 0;
+ 		p += prlen;
+ 	}
+ 	
+ 	if (LogStat & LOG_PID) {
+ 		prlen = snprintf(p, tbuf_len, "[%d]", getpid());
+ 		tbuf_len -= prlen;
+ 		if (tbuf_len < 0)
+ 		  tbuf_len = 0;
+ 		p += prlen;
+ 	}
+ 	if ((LogTag != NULL) && (tbuf_len > 2)) {
  		*p++ = ':';
  		*p++ = ' ';
+ 		tbuf_len -= 2;
+ 		if (tbuf_len < 0)
+ 		  tbuf_len = 0;
  	}
  
***************
*** 147,156 ****
  		if (ch == '%' && fmt[1] == 'm') {
  			++fmt;
! 			t += sprintf(t, "%s", strerror(saved_errno));
! 		} else
! 			*t++ = ch;
! 	*t = '\0';
  
- 	p += vsprintf(p, fmt_cpy, ap);
  	cnt = p - tbuf;
  
--- 179,202 ----
  		if (ch == '%' && fmt[1] == 'm') {
  			++fmt;
! 			prlen = snprintf(t, fmt_cpy_len,
! 					 "%s", strerror(saved_errno));
! 			t += prlen;
! 			fmt_cpy_len -= prlen;
! 		} else {
! 		        if (fmt_cpy_len-- > 0)
! 			  *t++ = ch;
! 		}
! 
! 	if (fmt_cpy_len-- > 0)
! 	  *t = '\0';
! 	else
! 	  fmt_cpy[FMT_CPYLEN-1] = '\0'; /* crock, but what else to do? */
! 
! 	prlen = vsnprintf(p, tbuf_len, fmt_cpy, ap);
! 	tbuf_len -= prlen;
! 	if (tbuf_len < 0)
! 	  tbuf_len = 0;
! 	p += prlen;
  
  	cnt = p - tbuf;
  
--cut here--------------------------------------------------------------------