sprintf(buffer, "%s […]", buffer, […]) 安全吗?

25

我在处理一些代码时,看到有人使用了这种模式将字符串连接起来:

sprintf(buffer, "%s <input type='file' name='%s' />\r\n", buffer, id);
sprintf(buffer, "%s</td>", buffer);

我相当确定这不是安全的C代码。你会注意到buffer既是输出也是第一个输入。

除了缓冲区溢出的明显可能性之外,我认为在函数执行期间没有保证buffer不会发生更改(即关于restrict的指定的目标字符串状态在函数执行时不确定)。sprintf的签名还指定了目标字符串是restrict的。

我还记得有一份关于memcpy中写入推测的报告,并且我看不出为什么某个C库不能在sprintf中做同样的事情。在这种情况下,当然会向其源中写入。那么这种行为是否安全呢?

供参考,我提议:

char *bufEnd = buffer + strlen(buffer);
/* sprintf returns the number of f'd and print'd into the s */
bufEnd += sprintf(bufEnd, " <input type='file' name='%s' />\r\n", id);

替换这个。


即使它是“安全的”(不会崩溃等),我也可以想象它生成的结果与预期的不同。 - nobody
@AndrewMedico 这样怎么样? - cat
3个回答

24

根据glibc sprintf()文档:

如果在重叠对象之间进行复制,例如如果在“%s”转换的控制下也将s作为参数打印,则此函数的行为是未定义的。

在特定实现中可能是安全的,但您不能指望它是可移植的。

我不确定您的提议在所有情况下都是安全的。您仍然可能重叠缓冲区。即使是在原始字符串在连接字符串中被重新使用并覆盖空字符的情况下,sprintf实现也可能不知道重新使用的字符串何时结束。

您可能只想将snprint()粘贴到临时缓冲区中,然后将其strncat()到原始缓冲区中。


2
好的,我只是想进行一次健全性检查。POSIX 也指出了同样的事情
如果由于调用 sprintf() 或 snprintf() 而在重叠对象之间发生复制,则结果是未定义的。
- Paul Fisher
实际上,我在第二个缓冲区中没有重叠缓冲区——它是一个完全不同的缓冲区。我不使用原始缓冲区。 - Paul Fisher
除非你看到了我没有看到的东西,这是完全有可能的。 - Paul Fisher
1
好的,我明白了;我想唯一有风险的情况就是如果您想在连接的字符串中某个时刻重新使用原始字符串。 - Karl Voigtland

5

在这种特定情况下,它会起作用,因为在buffer中的字符串将是第一件进入buffer(再次无用)的东西,所以您应该使用strcat()来获得[几乎]相同的效果。

但是,如果您尝试将strcat()sprintf()的格式化功能结合起来,可以尝试以下方法:

sprintf(&buffer[strlen(buffer)], " <input type='file' name='%s' />\r\n", id);

3
如果您想使用printf()将格式化文本连接到缓冲区的末尾,我建议您使用一个整数来跟踪结束位置。
int i = strlen(buffer);
i += sprintf(&buffer[i], " <input type='file' name='%s' />\r\n", id);
i += sprintf(&buffer[i], "</td>");

或者:

int i = strlen(buffer);
i += sprintf(&buffer[i], " <input type='file' name='%s' />\r\n", id);
strcat(&buffer[i], "</td>");

在人们开始疯狂地投票反对之前("这不安全!你可能会溢出缓冲区!"),我只是在讲述一种用C/C++构建格式化字符串的合理方法。


我认为您的建议在功能上与我的替代方案基本相同,只是使用了稍微不同的符号表示法。尽管如此,我能理解为什么有些人可能更喜欢以这种方式书写它。 - Paul Fisher

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接