简体   繁体   English

如何在没有格式字符串编译器警告的情况下正确编写 asnprintf?

[英]How to properly write asnprintf without format string compiler warnings?

I'd like to write a asnprintf function -- which is a wrapper around snprintf, but it mallocs the string according to its output size.我想写一个asnprintf function——它是 snprintf 的包装器,但它根据 output 的大小对字符串进行 malloc。 Unfortunately when I compile I get a warning (promoted to error on my system) format string is not a string literal [-Werror,-Wformat-nonliteral] .不幸的是,当我编译时,我收到一条警告(在我的系统上升级为错误) 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.我查看了警告,显然将非文字传递给printf函数存在安全问题,但就我而言,我需要采用格式指针并将其传递。

Is there a good way around this that does not expose the same security vulnerability?有没有解决这个问题的好方法,不会暴露相同的安全漏洞?

My function as is is as follows:我的function原样如下:

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

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:如果您只将 GCC 和 Clang 作为编译器,您可以通过暂时禁用该特定 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. Clang 也应该识别#pragma GCC You may also need to ignore -Wformat-security as I did above depending on your compiler flags.您可能还需要像我上面所做的那样忽略-Wformat-security ,具体取决于您的编译器标志。

Godbolt link to working example with Clang 11 . Godbolt 链接到 Clang 11 的工作示例


I'm wondering if it's possible for example to require that my function take only string literals我想知道是否有可能要求我的 function 只接受字符串文字

As Craig Estey suggests above , you could make use of the format function attribute to make the compiler perform this check for you: 正如 Craig Estey 在上面建议的那样,您可以使用format function 属性让编译器为您执行此检查:

#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.请注意,上面的代码使用了语句表达式 ( ({...}) ),它们是非标准扩展,可能可用也可能不可用,具体取决于您的编译器标志。

From my top comments...从我的热门评论...

Just add __attribute__((__format__(__printf__,3,4))) to your asnprintf declaration and/or definition.只需将__attribute__((__format__(__printf__,3,4)))添加到您的asnprintf声明和/或定义中。

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.而且,进一步的好处是它将根据格式字符串检查传递给asnprintf的可变参数 arguments。

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM