簡體   English   中英

如何在沒有格式字符串編譯器警告的情況下正確編寫 asnprintf?

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

我想寫一個asnprintf function——它是 snprintf 的包裝器,但它根據 output 的大小對字符串進行 malloc。 不幸的是,當我編譯時,我收到一條警告(在我的系統上升級為錯誤) format string is not a string literal [-Werror,-Wformat-nonliteral]

我查看了警告,顯然將非文字傳遞給printf函數存在安全問題,但就我而言,我需要采用格式指針並將其傳遞。

有沒有解決這個問題的好方法,不會暴露相同的安全漏洞?

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

如果您只將 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 也應該識別#pragma GCC 您可能還需要像我上面所做的那樣忽略-Wformat-security ,具體取決於您的編譯器標志。

Godbolt 鏈接到 Clang 11 的工作示例


我想知道是否有可能要求我的 function 只接受字符串文字

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

您也可以使用宏和一些編譯器內置函數來執行此操作,但需要一些技巧:

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

請注意,上面的代碼使用了語句表達式 ( ({...}) ),它們是非標准擴展,可能可用也可能不可用,具體取決於您的編譯器標志。

從我的熱門評論...

只需將__attribute__((__format__(__printf__,3,4)))添加到您的asnprintf聲明和/或定義中。

這將提示編譯器不要抱怨。

而且,進一步的好處是它將根據格式字符串檢查傳遞給asnprintf的可變參數 arguments。

所以:

#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