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()
来避免缓冲区溢出。