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
.