You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

sprintf字符串溢出风险与规避:代码示例相关技术疑问解答

IoT嵌入式项目字符串构建安全问题解析

我的IoT嵌入式项目采用gcc 4.2.1编译,C语言标准为gnu99、C++为gnu++11,此前通过IDE编译未收到警告,但在onlinegdb中模拟时发现字符串构建存在不安全问题。以下是可运行代码示例:

#include <stdio.h>
#include <string.h>

int main()
{
    // example 1 works as expected initially - I think it is unsafe
    char testString1[6];
    sprintf(testString1, "123");
    sprintf(testString1, "%s456", testString1);
    printf("%s\n", testString1); // 123456
    
    // example 2 doesn't work as I expected - I think it is intrinsically safe though
    char testString2[6];
    snprintf(testString2, 6, "123");
    snprintf(testString2, 6, "%s456", testString2);
    printf("%s\n", testString2); // 456
    
    // example 3 what I found works and I think is safe (EDIT: I understand now it isn't after reading comments)
    char testString3[6];
    snprintf(testString3, 6, "123");
    snprintf(testString3 + strlen(testString3), 6, "456");
    printf("%s\n", testString3); // 123456

    // example 4 modified example3 for safety
    char testString4[6];
    snprintf(testString4, 6, "123");
    snprintf(testString4 + strlen(testString4), sizeof(testString4) - strlen(testString4), "4567");
    printf("%s\n", testString4); // 12345

    return 0;
}

运行输出如下:

main.c: In function ‘main’:
main.c:9:29: warning: ‘456’ directive writing 3 bytes into a region of size between 1 and 6 [-Wformat-overflow=]
    9 |     sprintf(testString1, "%s456", testString1);
      |                             ^~~
main.c:9:5: note: ‘sprintf’ output between 4 and 9 bytes into a destination of size 6
    9 |     sprintf(testString1, "%s456", testString1);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
main.c:15:33: warning: ‘456’ directive output may be truncated writing 3 bytes into a region of size between 1 and 6 [-Wformat-truncation=]
   15 |     snprintf(testString2, 6, "%s456", testString2);
      |                                 ^~~
main.c:15:5: note: ‘snprintf’ output between 4 and 9 bytes into a destination of size 6
   15 |     snprintf(testString2, 6, "%s456", testString2);
      |     ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
123456
456
123456
12345

技术疑问解答

1. 示例1是否安全?

完全不安全。sprintf没有缓冲区容量限制,当前测试中testString1刚好能放下123456加终止符(共6字节),但只要输入字符串长度稍有变化,就会触发缓冲区溢出——破坏相邻内存数据,引发程序崩溃、数据篡改等问题。gcc给出的-Wformat-overflow=警告已经明确指出了这个风险。

2. 示例2为何未达到预期效果?

snprintf的第二个参数是包含终止符在内的最大写入字节数。第二次调用时,"%s456"展开后需要7字节(原123占3字节+456占3字节+终止符1字节),但testString2总容量只有6字节。snprintf会截断输出以避免溢出,但由于空间不足,最终只能写入456和终止符(共4字节),直接覆盖了原有的123,导致输出不符合预期。

3. 示例3是否真的安全?能否采用该方式构建字符串?

不安全,绝对不能用。第二次调用snprintf时,第二个参数写的是6,但testString3剩余可用空间只有2字节(原字符串123+终止符占4字节,总容量6字节)。snprintf尝试写入456+终止符共4字节,必然超出剩余空间,引发缓冲区溢出——只是当前测试没爆问题,实际嵌入式环境中可能导致严重故障。

补充说明:示例4的安全逻辑

示例4的处理方式是正确的:通过sizeof(testString4) - strlen(testString4)计算出剩余可用空间(包含终止符),将其作为snprintf的第二个参数,确保写入的字节数不会超过缓冲区剩余容量,从根本上避免了溢出问题。运行结果12345符合预期,说明截断逻辑正常生效。

内容的提问来源于stack exchange,提问作者rmarques

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.24 09:52:06