Bugtraq mailing list archives

Re: better snprintf replacement, anyone?


From: casper () HOLLAND SUN COM (Casper Dik)
Date: Wed, 23 Jul 1997 15:28:57 +0200


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;
}



Current thread: