better snprintf replacement, anyone?

Theo de Raadt (deraadt@CVS.OPENBSD.ORG)
Sat, 19 Jul 1997 20:03:46 -0600

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."

It is a rather annoying problem to not have snprintf() on these legacy
systems, because over the last year or so snprintf() calls have
started popping up nearly everywhere where security is an issue.

There's a couple thousand snprintf() calls in the OpenBSD source tree.

Many packages now come with their own snprintf() variants, sendmail,
who knows what else...

A common complaint and worry is that those snprintf() and vsnprintf()
variants may not emulate the _exact_ behaviour of the native system's
sprintf call. This can be an issue.

Here's something I threw together yesterday, to see if this technique
will help solve the problem. The code has only received a small
amount of testing (thanks to those who helped me test it, and pointed
out problems).

I'd like to hear from people if they come up with any further
improvements. Hopefully this can drop into any piece of code and work
exactly as expected.

Newer versions will show up at http://theos.com/~deraadt/snprintf.c

/*
* Copyright (c) 1997 Theo de Raadt
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/

#include <sys/param.h>
#include <sys/types.h>
#include <sys/mman.h>
#include <signal.h>
#include <stdio.h>
#if __STDC__
#include <stdarg.h>
#include <stdlib.h>
#else
#include <varargs.h>
#endif
#include <setjmp.h>

static int pgsize;
static char *curobj;
static int caught;
static jmp_buf bail;

static char *
msetup(str, n)
char *str;
size_t n;
{
char *e;

if (pgsize == 0)
pgsize = getpagesize();

if (n == 0) {
*str = '\0';
return 0;
}
curobj = (char *)malloc(n + pgsize * 2);
if (curobj == NULL)
return NULL;
e = curobj + n;
e = (char *)roundup((unsigned long)e, pgsize);
if (mprotect(e, pgsize, PROT_NONE) == -1) {
free(curobj);
curobj = NULL;
return NULL;
}
return (e - n - 2); /* XXX: why 2? you don't want to know */
}

static void
mcatch()
{
longjmp(bail, 1);
}

static void
mcleanup(str, n, p)
char *str;
size_t n;
char *p;
{
strncpy(str, p, n-1);
str[n-1] = '\0';
free(curobj);
if (mprotect((caddr_t)(p + n), pgsize,
PROT_READ|PROT_WRITE|PROT_EXEC) == 0)
return;
mprotect((caddr_t)(p + n), pgsize,
PROT_READ|PROT_WRITE);
}

int
#if __STDC__
snprintf(char *str, size_t n, char const *fmt, ...)
#else
snprintf(str, n, fmt, va_alist)
char *str;
size_t n;
char *fmt;
va_dcl
#endif
{
va_list ap;
int ret;
char *p;
void (*sigsegv)() = NULL;

#if __STDC__
va_start(ap, fmt);
#else
va_start(ap);
#endif

p = msetup(str, n);
if (p == NULL)
return 0;
if (setjmp(bail) == 0) {
sigsegv = signal(SIGSEGV, mcatch);
ret = vsprintf(p, fmt, ap);
}
va_end(ap);
mcleanup(str, n, p);
(void) signal(SIGSEGV, sigsegv);
return (ret);
}

int
vsnprintf(str, n, fmt, ap)
char *str;
size_t n;
char *fmt;
char *ap;
{
int ret;
char *p;
void (*sigsegv)() = NULL;

p = msetup(str, n);
if (p == NULL)
return 0;
if (setjmp(bail) == 0) {
sigsegv = signal(SIGSEGV, mcatch);
ret = vsprintf(p, fmt, ap);
}
mcleanup(str, n, p);
(void) signal(SIGSEGV, sigsegv);
return (ret);
}