I'd like to write a asnprintf function -- which is a wrapper around snprintf, but it mallocs the string according to its output size. Unfortunately when I compile I get a warning (promoted to error on my system) format string is not a string literal [-Werror,-Wformat-nonliteral].
I looked up the warning and apparently there are security concerns with passing a non-literal to printf functions, but in my case, I need to take in a format pointer, and pass that on.
Is there a good way around this that does not expose the same security vulnerability?
My function as is is as follows:
int
asnprintf(char **strp, int max_len, const char *fmt, ...)
{
int len;
va_list ap,ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
len = vsnprintf(NULL, 0, fmt, ap);
if ( len > max_len)
len = max_len;
*strp = malloc(len+1);
if (*strp == NULL)
return -1;
len = vsnprintf(*strp, len+1, fmt, ap2);
va_end(ap2);
va_end(ap);
return len;
}
2 Answers 2
If you are targeting only GCC and Clang as compilers, you can get around this pretty easily by temporarily disabling the warning for that specific function:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
// Function definition here...
#pragma GCC diagnostic pop
Clang should recognize #pragma GCC too. You may also need to ignore -Wformat-security as I did above depending on your compiler flags.
Godbolt link to working example with Clang 11.
I'm wondering if it's possible for example to require that my function take only string literals
As Craig Estey suggests above, you could make use of the format function attribute to make the compiler perform this check for you:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
int __attribute__((format(printf, 3, 4))) asnprintf(char **strp, int max_len, const char *fmt, ...) {
// ... implementation ...
}
#pragma GCC diagnostic pop
char global_fmt[100];
int main(void) {
char *res;
asnprintf(&res, 100, "asd"); // will compile
asnprintf(&res, 100, global_fmt); // will NOT compile
return 0;
}
You could also do this, with a bit of trickery, using a macro and some compiler built-ins:
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"
int internal_asnprintf(char **strp, int max_len, const char *fmt, ...) {
return printf(fmt);
}
#pragma GCC diagnostic pop
#define asnprintf(strp, maxlen, fmt, ...) ({ \
_Static_assert(__builtin_constant_p(fmt), "format string is not a constant"); \
internal_asnprintf(strp, maxlen, fmt, __VA_ARGS__); \
})
char global_fmt[100];
int main(void) {
char *res;
asnprintf(&res, 100, "asd"); // will compile
asnprintf(&res, 100, global_fmt); // will NOT compile
return 0;
}
Note that the above code makes use of statement expressions (({...})) which are a non-standard extension and may or may not be available depending on your compiler flags.
Comments
From my top comments ...
Just add __attribute__((__format__(__printf__,3,4))) to your asnprintf declaration and/or definition.
This will cue the compiler to not complain.
And, the further benefit is that it will check the variadic arguments passed to asnprintf against the format string.
So:
#include <stdio.h>
#include <stdlib.h>
#include <stdarg.h>
// put this in a .h file!?
int __attribute__((__format__(__printf__,3,4)))
asnprintf(char **strp, int max_len, const char *fmt, ...);
int
asnprintf(char **strp, int max_len, const char *fmt, ...)
{
int len;
va_list ap, ap2;
va_start(ap, fmt);
va_copy(ap2, ap);
len = vsnprintf(NULL, 0, fmt, ap);
if (len > max_len)
len = max_len;
*strp = malloc(len + 1);
if (*strp == NULL)
return -1;
len = vsnprintf(*strp, len + 1, fmt, ap2);
va_end(ap2);
va_end(ap);
return len;
}
fmemopenand/oropen_memstreamand do a singlefprintfinstead of reinventing it.fprintfwould still need afmtthat is not a string literal. OP's implementation is more or less the de-facto standardasnprintfimplementation, nothing wrong with it.v*printffunctions at compile time. The docs for gcc on this option specifically says that these functions are excluded regarding this warning.sprintfimpl. I've even used it in an answer here. But, I must not be understanding the impact/purpose of-Wformat-nonliteralas doingv*printfcalls are okay here [AFAICT]. But, with thev*versions, you can't check the args inapanyway. Maybeasnprintfneeds (e.g.)__attribute__((__format__(__printf__,3,4)))I've just compiled it semi-cleanly, but I had to changelen = vsnprintf(strp, len + 1, fmt, ap2);intolen = vsnprintf(*strp, len + 1, fmt, ap2);