Bugtraq mailing list archives

Re: better snprintf replacement, anyone?


From: sten () ERGON CH (Sten Gunterberg)
Date: Tue, 22 Jul 1997 18:23:31 +0200


On 22nd July, James Bonfield <jkb () MRC-LMB CAM AC UK> wrote:

[...] I wrote a worst-cast scenario function to determine the maximum length
of output for a sprintf style request. It doesn't handle unicode or anything
fancy, but does handle most things reasonably well. I'm not saying it's 100%
foolproof (eg it makes assumptions that we're not on anything bigger than a
64bit system), but it's a start.


Below follows some code I hacked together to do my own snprintf()
*without* having to parse the format string. The performance freaks
among you will frown on my waste of cycles, I'm sure :)

--Sten

/*
 * Variants of snprintf() and vsnprintf() with a definable callback
 * handler function to catch buffer overflows.
 *
 * Use it only if you have the "spare" cycles needed to effectively
 * do every snprintf operation twice! Why is that? Because everything
 * is first vfprintf()'d to /dev/null to determine the number of bytes.
 * Perhaps a bit slow for demanding applications on slow machines,
 * no problem for a fast machine with some spare cycles.
 *
 * You don't have a /dev/null? Every Linux contains one for free!
 *
 * On my Ultra-1 (143Mhz) this implementation of snprintf() runs
 * at 45% of the speed of native sprintf(). This leaves my with
 * "just" under 100k snprintf()'s per second to play with :-)
 *
 * Because the format string is never even looked at, all current and
 * possible future printf-conversions should be handled just fine.
 *
 * Written July 1997 by Sten Gunterberg (gunterberg () ergon ch)
 */

#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>

static void
default_handler (const char *func, int max, const char *fmt)
{
    int len;

    fprintf(stderr,
        "*** short buffer in %s() with max=%d and format=%s",
        func, max, fmt);
    if ((len = strlen(fmt)) > 0 && fmt[len-1] != '\n')
        fputc('\n', stderr);
    abort();
}

static void (*overflow_handler)() = default_handler;

void
set_nprintf_overflow_handler (void (*arg)(const char *, int, const char *))
{
    overflow_handler = arg;
}

static int
needed (const char *fmt, va_list argp)
{
    static FILE *sink = NULL;

    if (sink == NULL)
    {
        if ((sink = fopen("/dev/null", "w")) == NULL)
        {
            /* Hmm. Find a better way to handle this! */
            fprintf(stderr, "*** failed to open /dev/null ***\n");
            abort();
        }
    }

    return vfprintf(sink, fmt, argp);
}

int
snprintf (char *buf, int max, const char *fmt, ...)
{
    va_list argp;
    int bytes;

    va_start(argp, fmt);
    if (needed(fmt, argp) > max)
    {
        (*overflow_handler)("snprintf", max, fmt);
        exit(1); /* should never get here, really */
    }
    bytes = vsprintf(buf, fmt, argp);
    va_end(argp);

    return bytes;
}

int
vsnprintf (char *buf, int max, const char *fmt, va_list argp)
{
    if (needed(fmt, argp) > max)
    {
        (*overflow_handler)("vsnprintf", max, fmt);
        exit(1); /* should never get here, really */
    }

    return vsprintf(buf, fmt, argp);
}

#ifdef STANDALONE

int
main (int argc, char **argv)
{
    char buf[10];

    snprintf(buf, 10, "test %d\n", 123);        /* works */
    snprintf(buf, 10, "test %d\n", 123456);     /* fails */
}

#endif



Current thread: