[4998] in bugtraq

home help back first fref pref prev next nref lref last post

Re: better snprintf replacement, anyone?

daemon@ATHENA.MIT.EDU (Casper Dik)
Wed Jul 23 13:40:13 1997

Date: 	Wed, 23 Jul 1997 15:28:57 +0200
Reply-To: Casper Dik <casper@HOLLAND.SUN.COM>
From: Casper Dik <casper@HOLLAND.SUN.COM>
To: BUGTRAQ@NETSPACE.ORG
In-Reply-To:  Your message of "Tue, 22 Jul 1997 09:30:05 EDT." 
              <199707221330.JAA12927@ead37.ead.dsa.com>

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


Typical system V implementations (as well as older BSD implementations) have
an unsafe tmpfile().  This is true for Solaris (before 2.6 when we fixed it)
and in IRIX (last time I looked).  Perhaps we can check and compile a list?

>   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!)

This is also a problem as many implementations expect snprintf() to
always return a positive count; either bytes required (possible > n)

That's one of the more difficult things of implementing a new function;
what willbe teh exact semantics?  (the 2.5/2.5.1 __* function has slightly
different semantcis than the public 2.6 *snprintf())

Here's one I did and it also uses temporary files, but it uses
"mkstemp()" which should be save.

BTW, mkstemp() always uses open(,, 0600) so you get private tmpfiles;
tmpfile() is required to use the umask() in setting the mode.  Depending
on how this is implemented, there's a window in which a tmpfile() can be opened
by other processes.

A naive reimplementation of tmpfile() using mkstemp() fails the standard
conformance tests.  (s = mkstemp(template); unlink(template); return
fdopen(s,"w+)).  Solaris 2.6 does use mkstemp() but does an fchmod() after
unlink()ing the file so there's no race condition either.
Standard are stupid sometimes.


/*
 * snprintf() quicky using temporary files.
 * Not stress tested, but it seems to work.
 *
 * Returns the number of bytes that would have been output by printf.
 * Does not check whether a negative value if passed in for n (as it's unsigned);
 * some implementations cast n to int and than compare with 0.
 *
 * Casper Dik (Casper.Dik@Holland.Sun.COM)
 */

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

int snprintf(char * buf, size_t n, const char *fmt, ...)
{
        va_list ap;
        int ret;

        va_start(ap, fmt);
        ret = vsnprintf(buf, n, fmt, ap);
        va_end(ap);
        return ret;
}

static char template[] = "/tmp/snprintfXXXXXX";

int vsnprintf(char *buf, size_t n, const char *fmt, va_list ap)
{
        char templ[sizeof(template)];
        int s;
        int ret, cnt = 0, nread;
        FILE *fp;

        strcpy(templ,template);

        s = mkstemp(templ);

        if (s < 0)
                return -1;

        (void) unlink(templ);

        fp = fdopen(s, "w+");

        if (fp == NULL) {
                close(s);
                return -1;
        }

        ret = vfprintf(fp, fmt, ap);

        if (ret < 0 || fseek(fp, 0, SEEK_SET) < 0) {
                fclose(fp);
                return -1;
        }

        while (cnt < n && (nread = fread(buf + cnt, 1, n - cnt, fp)) > 0)
                cnt += nread;

        buf[cnt-1] = '\0';  /* cnt is atmost n */

        fclose(fp);
        return ret;
}

home help back first fref pref prev next nref lref last post