views:

390

answers:

7

Per man pages, snprintf is returning number of bytes written from glibc version 2.2 onwards. But on lower versions of libc2.2 and HP-UX, it returns a positive integer, which could lead to a buffer overflow.

How can one overcome this and write portable code?

Edit : For want of more clarity

This code is working perfectly in lib 2.3

 if ( snprintf( cmd, cmdLen + 1, ". %s%s", myVar1, myVar2 )  != cmdLen )
 {
  fprintf( stderr, "\nError: Unable to  copy bmake command!!!");
  returnCode = ERR_COPY_FILENAME_FAILED;
 }

It returns the length of the string (10) on Linux. But the same code is returning a positive number that is greater than the number of characters printed on HP-UX machine. Hope this explanation is fine.

A: 

Your question is a little unclear. Could you give an example?

Mike Thompson
A: 

I have found one portable way to predict and/or limit the number of characters returned by sprintf and related functions, but it's inefficient and many consider it inelegant.

What you do is create a temporary file with tmpfile(), fprintf() to that (which reliably returns the number of bytes written), then rewind and read all or part of the text into a buffer.

Example:

int my_snprintf(char *buf, size_t n, const char *fmt, ...)
{
    va_list va;
    int nchars;
    FILE *tf = tmpfile();

    va_start(va, n);
    nchars = vfprintf(tf, fmt, va);
    if (nchars >= (int) n)
        nchars = (int) n - 1;
    va_end(va);
    memset(buf, 0, 1 + (size_t) nchars);

    if (nchars > 0)
    {
        rewind(tf);
        fread(buf, 1, (size_t) nchars, tf);
    }

    fclose(tf);

    return nchars;   
}
finnw
+3  A: 

you could create a snprintf wrapper that returns -1 for each case when there is not enough space in the buffer.

See the man page for more docs. It has also an example which threats all the cases.

  while (1) {
      /* Try to print in the allocated space. */
      va_start(ap, fmt);
      n = vsnprintf (p, size, fmt, ap);
      va_end(ap);
      /* If that worked, return the string. */
      if (n > -1 && n < size)
         return p;
      /* Else try again with more space. */
      if (n > -1)    /* glibc 2.1 */
         size = n+1; /* precisely what is needed */
      else           /* glibc 2.0 */
         size *= 2;  /* twice the old size */
      if ((np = realloc (p, size)) == NULL) {
         free(p);
         return NULL;
      } else {
         p = np;
      }
   }
Iulian Şerbănoiu
This doesn't work well, because a current valid snprintf() can return -1 on error.
James Antill
The -1 case is treated when it allocates twice the old size (/* glibc 2.0 */ comment ).The printf function has 2 behaviours:1. It returns -1 to tell you that the buffer is not big enough2. It returns the number of bytes needed (you need to compare to see if it is ok)
Iulian Şerbănoiu
+2  A: 

Have you considered a portable implementation of printf? I looked for one a little while ago and settled on trio.

http://daniel.haxx.se/projects/trio/

+1  A: 

Your question is still unclear. The man page linked to speaks thus:

The functions snprintf() and vsnprintf() do not write more than size bytes (including the trailing '\0'). If the output was truncated due to this limit then the return value is the number of characters (not including the trailing '\0') which would have been written to the final string if enough space had been available. Thus, a return value of size or more means that the output was truncated.

So, if you want to know if your output was truncated:

int ret = snprintf(cmd, cmdLen + 1, ". %s%s", myVar1, myVar2 ) == -1)
if(ret == -1 || ret > cmdLen)
{
    //output was truncated
}
else
{
    //everything is groovy
}
Frosty
A: 

Use the much superior asprintf() instead.

It's a GNU extension, but it's worth copying to the target platform in the event that it's not natively available.

mbac32768
asprintf() isn't really better as it isn't std. at all and does behave differently. Also what you get isn't a usefully managed string, like with ustr_dup_fmt() etc.
James Antill
+1  A: 

There are a whole host of problems with *printf portability, and realistically you probably want to follow one of three paths:

  1. Require a c99 compliant *printf, because 9 years should be enough for anyone, and just say the platform is broken otherwise.

  2. Have a my_snprintf() with a bunch of #ifdef's for the specific platforms you want to support all calling the vsnprintf() underneath (understanding the lowest common denominator is what you have).

  3. Just carry around a copy of vsnprintf() with your code, for simple usecases it's actually pretty simple and for others you'd probably want to look at vstr and you'll get customer formatters for free.

...as other people have suggested you can do a hack merging #1 and #2, just for the -1 case, but that is risky due to the fact that c99 *printf can/does return -1 in certain conditions.

Personally I'd recommend just going with a string library like ustr, which does the simple workarounds for you and gives you managed strings for free. If you really care you can combine with vstr.

James Antill