Home > Software design >  How to properly write asnprintf without format string compiler warnings?
How to properly write asnprintf without format string compiler warnings?

Time:11-17

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

CodePudding user response:

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.

CodePudding user response:

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