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

我有以下代码:

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

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

谢谢。

是的,它很脆弱。

只需使用vsnprintf代替:

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

是的,它很脆弱。

您可以这样实现您的function:

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

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

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

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

§7.15

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

这个公式的一个缺点是它需要一个悲观的观点并无条件地两次调用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()调用的结果send()如果malloc()失败,可能会返回错误指示)。

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

如果你在Linux上,你可以使用vasprintf() ,它将分配一个正确大小的缓冲区。

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