运维123 - 安全编程
type: channel
link: https://www.sddts.cn/index.php/category/%E5%AE%89%E5%85%A8%E7%BC%96%E7%A8%8B/
description: 安全编程
items: strlcpy 与 strlcat：安全的 C 语言字符串操作

strlcpy 与 strlcat：安全的 C 语言字符串操作
type: item
date: 2026-02-02T23:15:26+00:00
description: strlcpy 与 strlcat：安全的 C 语言字符串操作一、概述1. 背景C 语言中的字符串操作一直是一个安全隐患的来源。传统的 strcpy() 和 strcat() 函数不进行边界检查，容易导致缓冲区溢出攻击。为了解决这个问题，C 标准库提供了 strncpy() 和 strncat() 函数，但这些函数的设计存在许多问题，使得正确使用它们变得困难。2. 问题陈述在 1996 年，OpenBSD 项目组在审计源代码时发现，程序员在尝试使用 strncpy() 和 strncat() 进行安全字符串操作时，经常误解其 API 语义，导致错误的使用。这些错误虽然不一定都能被利用，但表明 strncpy() 和 strncat() 的安全操作规则被广泛误解。3. 解决方案Todd C. Miller 和 Theo de Raadt 设计了两个新的函数 strlcpy() 和 strlcat()，提供了更直观、一致的 API，专门设计用于安全的字符串复制操作。二、传统函数的问题1. strncpy 的问题A. 不保证 NULL 终止strncpy() 只有在源字符串长度小于 size 参数时才会对目标字符串进行 NULL 终止。如果源字符串长度大于或等于 size，目标字符串将不会被 NULL 终止，这可能导致后续的字符串操作出现问题。B. 性能问题strncpy() 会用 NULL 字符填充目标字符串的剩余空间，当目标缓冲区远大于源字符串时，这会导致显著的性能损失。C. 零填充影响缓存大量的 NULL 填充操作会有效地刷新 CPU 的数据缓存，进一步降低性能。2. strncat 的问题A. 容易混淆的 size 参数strncat() 的 size 参数表示目标字符串中剩余的可用空间（不包括 NULL 终止符），而不是目标字符串的总大小。这个区别经常被程序员误解。B. 需要计算可用空间由于 size 参数是可用空间而非总大小，程序员必须手动计算 sizeof(dst) - strlen(dst) - 1，这个计算经常出错。三、新函数的设计1. 函数原型graph LR A[源字符串 src] -->|strlcpy| B[目标缓冲区 dst] B -->|返回| C[源字符串长度] D[目标字符串 dst] -->|strlcat| E[目标缓冲区 dst] F[源字符串 src] -->|strlcat| E E -->|返回| G[总长度]size_t strlcpy(char *dst, const char *src, size_t size); size_t strlcat(char *dst, const char *src, size_t size);2. 设计原则A. 保证 NULL 终止只要 size 参数非零，strlcpy() 和 strlcat() 保证目标字符串总是被 NULL 终止。B. 使用总大小作为参数两个函数都接受目标字符串的总大小作为 size 参数，通常可以直接使用 sizeof(dst)，无需复杂计算。C. 不进行零填充除了必需的 NULL 终止符外，函数不会填充目标字符串的剩余空间。D. 返回尝试创建的字符串总长度返回值表示如果没有截断会发生什么，这使得检测截断变得简单。四、使用示例对比1. 不安全的原始代码（示例 1a）strcpy(path, homedir); strcat(path, "/"); strcat(path, ".foorc"); len = strlen(path);这段代码存在缓冲区溢出的风险，因为 homedir 环境变量由用户控制，长度可以是任意的。2. 使用 strncpy/strncat 的安全版本（示例 1b）strncpy(path, homedir, sizeof(path) - 1); path[sizeof(path) - 1] = '\0'; strncat(path, "/", sizeof(path) - strlen(path) - 1); strncat(path, ".foorc", sizeof(path) - strlen(path) - 1); len = strlen(path);这个版本虽然安全，但代码复杂，容易出错：需要手动终止字符串每次调用 strncat 都要计算剩余空间仍然需要最终的 strlen 调用3. 使用 strlcpy/strlcat 的简单版本（示例 1c）strlcpy(path, homedir, sizeof(path)); strlcat(path, "/", sizeof(path)); strlcat(path, ".foorc", sizeof(path)); len = strle…