Re: better snprintf replacement, anyone?

Bill Rugolsky Jr. (rugolsky@EAD.DSA.COM)
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;
}