C中va_list可能存在缓冲区溢出漏洞?

时间:2011-08-27 16:47:40

标签: c overflow buffer variadic-functions

我有以下代码:

int ircsocket_print(char *message, ...)
{
    char buffer[512];
    int iError;
    va_list va;
    va_start(va, message);
    vsprintf(buffer, message, va);
    va_end(va);
    send(ircsocket_connection, buffer, strlen(buffer), 0);
    return 1;
}

我想知道这个代码是否通过提供大小为>的char数组来缓冲溢出是否有用。 512到变量列表?如果是这样 - 我该如何解决这个问题?

谢谢。

4 个答案:

答案 0 :(得分:10)

是的,它很脆弱。

您可以这样实现您的功能:

int ircsocket_print(char *message, ...)
{
    char buf[512];
    char *buffer;
    int len;
    va_list va;

    buffer = buf;
    va_start(va, message);
    len = vsnprintf(buffer, 512, message, va);
    va_end(va);

    if (len >= 512)
    {
        buffer = (char*)malloc(len + 1);
        va_start(va, message);
        len = vsnprintf(buffer, len + 1, message, va);
        va_end(va);
    }

    send(ircsocket_connection, buffer, len, 0);

    if (buffer != buf)
        free(buffer);
    return 1;
}

答案 1 :(得分:8)

是的,它很脆弱。

只需使用vsnprintf代替:

vsnprintf(buffer, sizeof(buffer), message, va);

答案 2 :(得分:6)

正如其他人所说,问题的基本答案是“是的,你很容易受到缓冲区溢出的影响”。

在C99中,您可以使用VLA:

void ircsocket_print(const char *fmt, ...)
{
    va_list args;

    va_start(args, fmt);
    int len = vsnprintf(0, 0, message, args);
    va_end(args);

    char buffer[len+1];

    va_start(args, fmt);
    int len = vsnprintf(buffer, len+1, message, args);
    va_end(args);

    send(ircsocket_connection, buffer, len, 0);
}

注意使用长度为0(第二个零)调用vsnprintf()一次以获取所需长度的习惯用法,然后再次调用它以将数据格式化为缓冲区。另请注意每次致电args后仔细重置vsnprintf();这是C标准所要求的:

  

§7.15<stdarg.h>

     

如果访问不同的参数是   期望,被调用的函数应声明一个对象(在此通常称为ap   子条款)类型va_list。对象ap可以作为参数传递给   另一个功能;如果该函数使用参数va_arg调用ap宏,则   调用函数中ap的值是不确定的,并且应该传递给va_end   在进一步引用ap之前的宏。

这种表述的一个缺点是它需要一个悲观的观点并无条件地调用vsnprintf()两次。您可能更喜欢采取乐观的观点(大多数情况下,512就足够了),并且只有在第一次调用显示它不足时才分配更多空间。

使用像这样的VLA的另一个缺点是,如果你的局部变量buffer的空间不足,你的代码可能永远不会有机会恢复。你必须判断问题有多严重。如果是一个问题,请改用显式内存分配(malloc()):

char buffer = malloc(len+1);
if (buffer == 0)
    return;  // Report error?
...second vsnprintf() and send()...
free(buffer);

由于你的函数只返回常量1,所以没有明显的理由让它成为返回任何东西的函数 - 如果它是一个返回void的函数,则调用代码不需要任何检查在返回的值上。 OTOH,也许您应该返回send()调用的结果(如果malloc()失败,可能会出现错误指示)。

我还将格式(消息)参数改为const char *;这个函数和它调用的函数都不会修改格式字符串。

答案 3 :(得分:2)

如果您使用的是Linux,则可以使用vasprintf()来分配正确大小的缓冲区。

如果您需要可移植性,可以使用vsnprintf()来避免缓冲区溢出。