Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the difference between vsnprintf and vsprintf_s?

I am currently, writing a code for string manipulation. As part of this, I am using vsnprintf().

However, compiler flashes below error message:

dont_call: vsnprintf(). Invokation of a potentially dangerous function
    that could introduce a vulnerability. remediation:
    Recommendation: Use vsprintf_s() instead!

The results with vsprintf_s() is as not expected.

What is the difference between vsnprintf() and vsprintf_s()?

like image 600
Sanman Avatar asked Sep 29 '17 09:09

Sanman


2 Answers

vsnprintf() is a perfectly fine standard function that has no security issues if used properly, especially if the format string is a string literal compatible with the arguments passed.

Microsoft deprecated this function because an attacker could take advantage of sloppy code that uses user supplied text as the format specification: carefully crafted user supplied strings could use the %n format to try and corrupt the program's data and make the program execute arbitrary code.

Using a variable format string is error prone as it is difficult to verify that the types of the arguments are compatible with this dynamically generated format string. Sensible coding conventions do not permit such code. Passing a user supplied string as a format string is completely inappropriate as it is a trivial source of undefined behavior. Disabling useful and standard functions because they could be misused and create security flaws is laudable, but in this particular case, the proposed alternatives have shortcomings.

There are 2 alternatives for vsnprintf(), standardized in Annex K, but with optional support and thus non portable across environments:

int vsprintf_s(char * restrict s, rsize_t n,
               const char * restrict format,
               va_list arg);

and

int vsnprintf_s(char * restrict s, rsize_t n,
                const char * restrict format,
                va_list arg);

These functions behave differently from vsnprintf in subtile ways:

  • Neither support the %n specifier.
  • Neither allow the size argument n to have a 0 value.
  • Neither allow the s target array to be a null pointer, which snprintf() allows if the n size is 0.
  • vsprintf_s differs from vsnprintf_s when the output string is longer than n-1: it sets the destination to the empty string, invokes the error handler and returns 0 or a negative number instead of the length of the formatted string, as both vsnprintf and vsnprintf_s do.

As commented by A T, to make matters worse, Microsoft implements different semantics in their own version of this standard functions: even the prototype for vsnprintf_s is different (as documented in the Visual Studio documentation):

int vsnprintf_s(
   char *buffer,
   size_t sizeOfBuffer,   // extra argument
   size_t count,          // special semantics for the value _TRUNCATE
   const char *format,
   va_list argptr
);

As a result of the numerous deviations from the specification the Microsoft implementation cannot be considered conforming or portable.

You did not post the code where you want a replacement for snprintf(), but a classic use case is this:

/* allocate a string formated according to `format` */
int vasprintf(char **strp, const char *format, va_list ap) {
    va_list arg;
    int ret;

    if (!strp) {
        errno = EINVAL;
        return -1;
    }

    va_copy(arg, ap);
    ret = vsnprintf(NULL, 0, format, arg);
    va_end(arg);

    *strp = NULL;
    if (ret < 0 || (*strp = malloc(ret + 1)) == NULL) {
        return -1;
    }

    return vsnprintf(*strp, ret + 1, format, ap);
}

In the above code, vnsprintf cannot be replaced with vsprintf_s because vsprintf_s cannot be used to compute the required size: it always considers a short size as an error. vsnprintf_s cannot be used as a direct replacement because it considers a NULL pointer as the target to be an error too.

The solution is simple: you can prevent Visual Studio from emitting this warning by defining a macro before including <stdio.h> in the source file:

#ifdef _MSC_VER
#define _CRT_SECURE_NO_WARNINGS  // let me use standard functions
#endif

This prevents the warning for all deprecated library functions.

Note also that it is very useful to increase the warning level and let the compiler warn about potential bugs, such as inconsistent arguments for a given format string. With gcc and clang, you can pass -Wall and other command line options to enable such behavior. For Visual Studio, you can add more warnings with /W4.

like image 111
chqrlie Avatar answered Nov 09 '22 20:11

chqrlie


The solution is to always add

#define _CRT_SECURE_NO_WARNINGS

as the first line of code when compiling with Visual Studio. Or maybe the second line if you're writing cross-platform code and the first line is something like #ifdef _WIN32, such as

#ifdef _WIN32
#define _CRT_SECURE_NO_WARNINGS
#endif

This will disable warnings that Microsoft has "deprecated" functions required by the C Standard.

like image 32
Andrew Henle Avatar answered Nov 09 '22 19:11

Andrew Henle