flawopen.com/内存安全/strcpy と strncpy
strncpy() 比 strcpy() 更安全吗?
CWE-787:越界写入;CWE-170:空字符终止不当参考页面
简要结论
固定宽度字段就像一排格子:如果后续代码把字节当作 C 字符串,末尾必须留出结束标记。strncpy() 最多写入 n 个字节;只有当 n 不大于真实缓冲区容量时,才不会写出目标范围。如果源内容填满限制,函数不会添加空字符。后续字符串函数可能越过目标缓冲区寻找缺失的标记。
两种写法都有问题
char dst[16]; /* 1. Unbounded — classic overflow */ strcpy(dst, src); /* 2. Bounded, but may not terminate. If strlen(src) >= 16, dst has no '\0' and every later strlen/printf reads out of bounds. */ strncpy(dst, src, sizeof(dst)); /* 3. Off-by-one: room for 16 chars plus a terminator = 17 bytes, but dst only holds 16. */ strncpy(dst, src, sizeof(dst)); dst[sizeof(dst)] = '\0'; /* OOB! */
正确做法
char dst[16]; /* Preferred: snprintf always null-terminates (when size > 0), and tells you about truncation. */ int n = snprintf(dst, sizeof(dst), "%s", src); if (n < 0 || (size_t)n >= sizeof(dst)) { /* truncated — handle it */ return ERR_TOO_LONG; } /* Or strlcpy where available — always terminates, returns the length it tried to create. */ if (strlcpy(dst, src, sizeof(dst)) >= sizeof(dst)) { return ERR_TOO_LONG; }
strncpy 最初的设计用途
这种行为并非缺陷:strncpy() 是为固定宽度、以空字符填充的字段设计的,并非通用安全的 C 字符串复制函数。如果源字符串短于 n,函数会用空字节填充目标区直到 n - 1。如果源的前 n 个字节中没有空字节,它就精确复制 n 个字节且不终止目标区。这适用于某些固定宽度字段格式,但传给要求空字符终止字符串的 API 时很危险。
当源短于 n 时,填充操作会写满整个字段。对于大缓冲区和短字符串,这可能比遇到终止符就停止的复制多做工作。
始终检查截断
防止越界只是任务的一半。静默截断本身也可能造成安全问题:被截断的主机名、路径或授权值可能改变含义。snprintf 返回它本来会写入的长度;将其与缓冲区大小比较即可检测截断。误把返回值当成实际写入字节数,也与 Citrix Bleed 有关。
常见问题
strcpy_s 和 Annex K 函数呢?
它们会终止字符串并检查边界,但可移植性不一致:Windows 支持较好,其他环境的实现参差不齐。面向多个平台时,snprintf 通常更容易使用。
strlcpy 是 C 标准的一部分吗?
不是。它起源于 OpenBSD,在 BSD 和 macOS 上广泛可用;较新的 glibc 也已加入。请确认目标平台是否提供它。
在 C++ 中应该用这些函数吗?
优先使用 std::string 和 std::string_view 管理长度。只有在必须接收原始缓冲区的 API 边界才使用 C 函数。