Bugtraq mailing list archives

Re: better snprintf replacement, anyone?


From: rugolsky () EAD DSA COM (Bill Rugolsky Jr.)
Date: Tue, 22 Jul 1997 09:30:05 -0400


On Sat, 19 Jul 1997, Theo de Raadt wrote:
Quite often I find people saying to me "Why do you use snprintf() all
over the place to avoid buffer overflows, and not try to use other
techniques.  Using snprintf() makes it hard for us to port the code to
legacy systems."

A while back I threw together these short routines, which have worked quite
well in practice.  The return value conventions probably don't correspond to
the BSD snprintf(), because I didn't have any documentation available.
The principal virtue is that they produce results identical to the native
printf() implementation.  They were not written with security in mind, though.
A poor implementation of tmpfile() could open up numerous security holes.

Hope you find them useful.

   Bill Rugolsky
   rugolsky () ead dsa com

------------------------------ cut here ------------------------------

#include <stdio.h>
#include <stdarg.h>
#include <errno.h>
#include <malloc.h>

/*
   The variable "snprintf_bufsiz" may be set prior to calling any
   of these functions.  Set this value large enough to avoid
   doing any system calls.
*/

size_t snprintf_bufsiz = 4*BUFSIZ;

static FILE *fp; /* temporary file used as a buffer */

static FILE *
getfilebuffer()
{
   FILE *f;

   if ((f = tmpfile()) != NULL)
      setvbuf(f,NULL,_IOFBF,snprintf_bufsiz);
   return f;
}

/*
   [v]snprintf:  A safe alternative to [v]sprintf.  This version is
   may be significantly slower, especially on SunOS, where the
   implementation of stdio is sub-optimal.  In particular, rewind()
   may generate a system call (aargh!).  A good implementation of
   stdio will incur only a memcpy() penalty from the fread().

   If the null-terminated string fits inside of the buffer, returns
   the string length (not counting the terminating '\0').

   If the string exceeds the buffer length, errno is set to ENOMEM
   and the negative string length is returned.  The first len-1
   characters are placed in the buffer.
   (Add one for the terminating '\0' when allocating a buffer!)

   Any other error causes the function to set errno and return -1.
*/


int
vsnprintf(char buf[], size_t len, const char fmt[], va_list args)
{
   int actual;

   if(!buf || len < 1) {
      errno = EINVAL;
      return -1;
   }

   if (!fp && !(fp = getfilebuffer()))
      return -1;
   rewind(fp);
   actual = vfprintf(fp,fmt,args);
   rewind(fp);
   if (actual < 0)
      return -1;
   else if (actual < len)
      len = actual;
   else
      --len;
   if (len > 0 && fread(buf,1,len,fp) != len)
      return -1;
   buf[len] = '\0';

   if (actual > len) {
      errno = ENOMEM;
      return -actual;
   }
   return actual;
}

int
snprintf(char buf[], size_t len, const char fmt[], ...)
{
   int rv;
   va_list args;

   va_start(args,fmt);
   rv = vsnprintf(buf,len,fmt,args);
   va_end(args);
   return rv;
}

/*
   [v]mprintf: format string into a buffer allocated using malloc().
*/

char *
vmprintf(const char fmt[], va_list args)
{
   int actual;
   char *buf;

   if (!fp && !(fp = getfilebuffer()))
      return NULL;
   rewind(fp);
   actual = vfprintf(fp,fmt,args);
   rewind(fp);
   if (actual < 0)
      return NULL;
   if (!(buf = malloc(actual+1)))
      return NULL;
   if (fread(buf,1,actual,fp) != actual) {
      free(buf);
      return NULL;
   }
   buf[actual] = '\0';

   return buf;
}

char *
mprintf(const char fmt[], ...)
{
   char *s;
   va_list args;

   va_start(args,fmt);
   s = vmprintf(fmt,args);
   va_end(args);
   return s;
}



Current thread: