Day 10:字符串操作函数的安全隐患(如 strcpy, strcat, gets)

1. 主题原理与细节逐步讲解

C语言的字符串是一组以'\0'结尾的字符数组,标准库为其提供了多种操作函数,如strcpy, strcat, gets, sprintf等。这些函数有一个共同特点:不会自动校验目标缓冲区的大小,极易发生缓冲区溢出,进而引发严重的安全问题。

  • strcpy(dest, src):从src复制到dest,遇到\0结束。不检查dest空间是否足够。
  • strcat(dest, src):将src追加到dest末尾,同样不检查dest剩余空间。
  • gets(buf):从标准输入读取一行内容,直到回车,不检查buf长度(已被C11标准移除)。
  • sprintf(buf, ...):格式化输出到buf,不检查长度。
  • scanf("%s", buf):读取字符串,不限制长度。

本质问题:这些函数都将缓冲区大小校验的责任完全留给了调用者,一旦忽略,后果严重。


2. 典型陷阱/缺陷说明及成因剖析

典型陷阱

  • 目标缓冲区太小,源数据太大,导致缓冲区溢出,覆盖相邻内存。
  • 字符串拼接多次,未累积长度判断
  • 未给scanf加长度限定

成因剖析

  • C语言设计早期追求高性能和简单实现,没有内建安全检查。
  • 程序员容易高估或遗忘目标缓冲区大小,尤其在处理外部输入时。
  • 这些函数接口不包含长度参数,容易误用。

3. 规避方法与最佳设计实践

  • 永远不用getsstrcpystrcatsprintfscanf("%s", ...)等“危险函数”
  • 优先使用带长度限制的安全版本:
    • fgets(buf, size, stdin) 代替 gets
    • strncpy(dest, src, n) 代替 strcpy(注意 strncpy 不保证结尾加 \0,需手动处理)
    • strncat(dest, src, n) 代替 strcat
    • snprintf(buf, size, ...) 代替 sprintf
    • scanf("%Ns", buf) 指定最大长度(N为缓冲区-1)
  • 始终预留一个字节存放\0结尾符
  • 对于复杂字符串拼接,建议封装“安全字符串操作”工具函数。
  • 定期使用静态分析工具和代码审查查找潜在溢出点

4. 典型错误代码与优化后正确代码对比

错误代码示例

char buf[8];
strcpy(buf, "123456789"); // 目标8字节,源9字节(含\0),溢出!

char name[16];
gets(name); // 用户随便输,轻松溢出

正确代码示例

char buf[8];
strncpy(buf, "123456789", sizeof(buf) - 1);
buf[sizeof(buf) - 1] = '\0'; // 强制结尾安全

char name[16];
fgets(name, sizeof(name), stdin); // 最多读15字节,自动加\0

机制差异分析

  • strcpy/gets:不关心目标长度,输入超长直接溢出;
  • strncpy/fgets:明确限制最大写入长度,安全系数高得多。strncpy需手动补\0

5. 必要底层原理解释

  • C字符串数组本质只是连续内存,标准库函数不会为你检查边界。
  • 溢出会覆盖局部变量、返回地址、堆栈结构等,易被攻击者利用,导致代码执行劫持(如栈溢出攻击)。
  • gets被C11标准彻底移除,许多现代编译器已发出警告或禁止使用。

6. SVG辅助图示

<svg width="420" height="100">
  <rect x="30" y="40" width="80" height="30" fill="#bdf" stroke="#000"/>
  <rect x="110" y="40" width="80" height="30" fill="#fdc" stroke="#000"/>
  <rect x="190" y="40" width="80" height="30" fill="#ffc" stroke="#000"/>
  <text x="35" y="60" font-size="13">buf[8]</text>
  <text x="115" y="60" font-size="13">变量A</text>
  <text x="195" y="60" font-size="13">返回地址</text>
  <line x1="70" y1="35" x2="70" y2="15" stroke="#c00" stroke-width="2" marker-end="url(#arrow)"/>
  <text x="10" y="12" font-size="12" fill="#c00">溢出覆盖关键区</text>
  <defs>
    <marker id="arrow" markerWidth="8" markerHeight="8" refX="4" refY="4"
            orient="auto" markerUnits="strokeWidth">
      <path d="M0,0 L8,4 L0,8 L2,4 L0,0" fill="#c00"/>
    </marker>
  </defs>
</svg>

7. 总结核心要点与实际建议

  • C标准库中很多字符串函数默认不安全,开发时必须主动防范溢出。
  • 永远不用gets,用fgets;不用strcpy,用strncpy并手动补\0
  • 为每一个字符串操作都明确指定最大长度,并合理处理结尾符。
  • 代码评审和静态分析是发现此类问题的利器。
  • 安全编程习惯:只用“安全API”+边界意识+代码审查,才能远离溢出风险。

实际建议:在团队和项目中统一采用“带长度限定的字符串操作函数”,并封装常用的安全字符串操作工具,形成强制规范,彻底杜绝溢出漏洞。

公众号 | FunIO
微信搜一搜 “funio”,发现更多精彩内容。
个人博客 | blog.boringhex.top

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值