C语言 如何正确地编写asnprintf而不出现格式字符串编译器警告?

o7jaxewo  于 2022-12-03  发布在  其他
关注(0)|答案(2)|浏览(143)

我想写一个asnprintf函数--它是snprintf的一个 Package 器,但是它会根据输出的大小来mallocs字符串。不幸的是,当我编译的时候,我得到了一个警告(在我的系统上升级为error)format string is not a string literal [-Werror,-Wformat-nonliteral]
我查看了警告,很明显,向printf函数传递非文本存在安全问题,但在我的示例中,我需要接受一个格式指针,并传递它。
有没有一个好的方法来解决这个问题,而不会暴露同样的安全漏洞?
我的职责如下:

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

zbwhf8kr1#

如果您只将愚者和Clang作为编译器,您可以通过暂时禁用该特定函数的警告来轻松解决这个问题:

#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wformat-nonliteral"
#pragma GCC diagnostic ignored "-Wformat-security"

// Function definition here...

#pragma GCC diagnostic pop

Clang也应该能识别#pragma GCC。你可能还需要像我上面做的那样忽略-Wformat-security,这取决于你的编译器标志。
Godbolt链接到Clang 11的工作示例。
我想知道是否可以要求我的函数只接受字符串
正如上面克雷格Estey所建议的,可以利用format函数属性让编译器为您执行此检查:

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

您也可以使用一个宏和一些编译器内置函数来实现这一点,但需要一些技巧:

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

请注意,上面的代码使用了语句表达式(({...})),这是一个非标准扩展,可能可用,也可能不可用,具体取决于您的编译器标志。

8wtpewkr

8wtpewkr2#

从我的顶部评论...
只需将__attribute__((__format__(__printf__,3,4)))添加到asnprintf声明和/或定义中。
这将提示编译器 * 不 * 抱怨。
另外,它还可以根据格式字符串检查传递给asnprintf的变量参数。
因此:

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

相关问题