Strlcpy和strlcat-一致的、安全的字符串复制和串接

时下缓冲区溢出攻击已经增加,越来越多的程序员使用带有 size 或长度边界的字符串函数,例如: strncpy 和 strncat 。这的确是一个趋势,但标准的 C 字符串函数并不是真正为这些任务而设计的。本文描述一个专门设计用于安全字符串复制的可选的、直觉的和一致的 API 。

将 strncpy 和 strncat 作为 strcpy 和 strcat 安全版本有几个问题。两个函数都是以不同的和非直觉的方法来处理 NULL 结尾的和长度参数,即使有经验的程序员都有时迷惑;而检查什么时候发生截断也是不容易的。最后, strncpy 用 0 来填充目标字符串剩余的部分,这是以损失性能为代价的。所有这些迷惑都是由长度参数引起的,空结束的要求也非常重要。当我们评估 OpenBSD 源树的潜在安全漏洞的时候,我们发现大量滥用 strncpy 和 strncat 。当然,并不是所有的都导致暴露的安全漏洞,上面的这些使说明了一点:使用 strncpy 和 strncat 作为安全字符串操作容易被误解。推荐使用的函数是 strlcpy 和 strlcat ,通过为安全字符串设计的 API 来程序这些问题(见图 1 的函数原型)。两个函数都保证NUL 结尾,长度参数是以字节记数的,并且提供了检查截断的方法,两个函数都不是在目标字符串中填充 0 。

 

介绍

 

在 1996 年中,作者和其他 OpenBSD 项目的成员一起承担了对 OpenBSD 源树的评估,为了找出安全问题;以缓冲区溢出作为开始。缓冲区溢出最近在一些论坛(例如 BugTraq )大量关注,并且正被广泛地开拓。我们发现大量的缓冲区溢出是由于较大的使用 sprintf 、 strcpy 、strcat 进行的字符串复制;在循环中操作字符串而没有明确地检查循环变量的长度也是一个问题。另外,我们也发现许多程序员使用 strncpy 和strncat 来进程安全字符串操作但失败的场景。

因此,在评估代码的时候,我们发现不仅仅检查 strcpy 和 strcat 的不安全使用,同样也要检查 strncpy 和 strncat 的不正确使用。检查正确使用并不总是明显地,特别在静态变量和缓冲区或 calloc 分配的缓冲区,这些都容易被忽视。我们得到结论,一个安全的 strncpy 和 strncat 是必要的,首先可以减轻程序员的工作;另外也可以是代码评估更容易。

 

size_t strlcpy(char *dst, const char *src, size_t size);
size_t strlcat(char *dst, const char *src, size_t size);

Figure 1: ANSI C prototypes for strlcpy() and strlcat()

通常误解

最通常的误解是 strncpy 空结尾的目标字符串。然而,只有源符串的长度小于 size 参数才是正确的。当用户输入的任意长度字符串时候,可能有问题。这种情况下最安全方法是传递一个小于目标字符串的 size 给 strncpy ,并且手动添加一个结束符号。这种方法下你可以总是保证目标字符穿是 NUL 结束的。严格地说,如果是一个静态的字符串或一个 calloc 分配的字符串不必要手动添加一个结束符号;主要由于这些字符串在分配的时候是填充 0 的。然而这些特性时候比较迷惑的。

另外一个暗示的假定就是从 strcpy 到 strcat 代码转换到 strncpy 和 strncat 导致的性能下降是可以接受的。对于 strncat 来说是正确的,但同样对于 strncpy 来说并不正确,由于 strncpy 将剩余的目标字节填充 0 ,这在字符串比较大的时候可能导致可观的性能损失。确切的损失由CPU 架构和实现的而决定。

最常见的错误是使用 strncat 和一个不正确的 size 参数。然而 strncat 保证目标字符串是 NULL 结尾的,你不需要在 size 参数中为 NUL 计算机空间。最重要的,这不是目标字符串自身的大小,而是可用空间的数量。因此这个值总是要计算,并且作为一个可靠的常量,它常常也不能正确计算。

为什么 strlcpy 和 strlcat 能够安全

 

这两个函数提供了一个一致的、没有二义性的 API 来帮助程序员写比较防弹代码。首先也是最重要的,两个函数都能够保证所有的目标字符串是NUL 结尾的,给定的 size 非 0 ;其次,两个函数都将目标字符串的整个 size 作为一个 size 参数。在大多数情况下,这个值比较容易在编译期间使用 sizeof 操作符号来计算;最后,不管是 strlcpy 还是 strlcat 都不 0 填充他们的目标字符串(而是强迫 NUL 到字符串的结尾)。

Strlcpy 和 strlcat 函数返回最终创建的字符串长度。对于 strlcpy 来说是源的长度,对于 strlcat 来说意味着目标的长度加源的长度。为了检查截断,程序员需要验证返回值是否小于 size 参数。因此,如果发生截断,可以发现已经存储了多少个字节,并且程序员可以重新分配空间来重新复制字符串。返回值和 snprintf 在 BSD 上的实现有相同的含义。如果没有截断发生,程序员现在有返回值长度的字符串;这是有用的,因为通常情况用 strncpy 和 strncat 来构造字符串并且使用 strlen 来取得字符串的长度。使用 strlcpy 和 strlcat , strlen 就不需要了。

例子 1a 是潜在缓冲区溢出的例子( HOME 环境变量由用户来控制可以是任意长度)。

strcpy(path, homedir);
strcat(path, "/");
strcat(path, ".foorc");
len = strlen(path);

Example 1a: Code fragment using strcpy() and strcat()

例子 1b 转换到 strncpy 和 strncat 的同样代码片段(注意,我必须自己添加字符串结束符号)。

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);

Example 1b: Converted to strncpy() and strncat()

例子 1c 是到 strlcpy/strlcat 的变化,其有例子 1a 一样简单的好处,但却没有利用 API 的返回值:

strlcpy(path, homedir, sizeof(path));
strlcat(path, "/", sizeof(path));
strlcat(path, ".foorc", sizeof(path));
len = strlen(path);

Example 1c: Trivial conversion to strlcpy()/strlcat()

由于例子 1c 如此容易阅读和理解,添加其他的检查也是非常简单,在例子 1d 中,我们检查返回值来确保对于源字符串来说有足够的空间。如果没有,我们返回一个错误。这里稍微复杂一点,但它仍然很好,同时也避免了调用 strlen 。

len = strlcpy(path, homedir,sizeof(path);
if (len >= sizeof(path))
return (ENAMETOOLONG);
len = strlcat(path, "/",sizeof(path);
if (len >= sizeof(path))
return (ENAMETOOLONG);
len = strlcat(path, ".foorc",sizeof(path));
if (len >= sizeof(path))
return (ENAMETOOLONG);

Example 1d: Now with a check for truncation

 

设计决策

许多思想加入判断 strlcpy 和 strlcat 应该是什么语义。最初的想法是使 strlcpy 和 strlcat 与 strncpy 和 strncat 相同,并且始终是 NUL 结束的目标字符串。然而,当我们回过来看常用(和误用) strncat 说服我们 strlcat 的 size 参数应该是字符串的所有大小而不仅仅是未分配的字符的数量。返回值开始作为复制字符串的数量,由于有复制和串联的副作用。我们很快决定返回值应该与 sprintf 一样,这样程序可以比较弹性的处理截断和恢复。

性能

当目标字符串的长度比源字符串明显大很多的时候,程序员正在避免使用 strncpy ,主要由于其降低性能。例如, Apache 组用内部函数来代替strncpy 并且注意到性能提升。同样, ncurses 包最近删除了 strncpy ,结果比 tic 实现提高了四倍。我们的希望是,将来更多的程序员使用strlcpy 而不是自定义的接口。

为了对性能的降低有一个感觉,我们比较 strncpy 和 strlcpy ,并且复制字符串 ’’ ;也就是复制 1000 次到 1024 字节的缓冲区中。这对strncpy 有点不公平,由于使用了大的缓冲区和小的字符串,并且大缓冲区的时候, strncpy 不得不填充多余的缓冲为 NUL 字符。实际上,通常使用的缓冲区都比用户输入的大,例如,路径名称缓冲区是 MAXPATHLEN 长( 1024 ),但多数文件都比较短。表 1 中的平均运行时间在HP9000/425t , 25Mhz68040 CPU 运行 OPENBSD2.5 , DEC AXPPCI166 上 166Mhz Alpha CPU 运行 OpenBSD 。所有的 case 都是相同 C 版本函数,时间由时间工具产生:


 cpu architecture 


 function 


 time (sec) 


m68k


strcpy


0.137


m68k


strncpy


0.464


m68k


strlcpy


0.14


alpha


strcpy


0.018


alpha


strncpy


0.10


alpha


strlcpy


0.02

表 1 :性能时间表

如我们在表 1 中看到的一样, strncpy 的时间是最坏的。可能的原因不仅是 NUL 填充,也可能因为 CPU 数据缓冲区被长流 0flush 的原因。

 

什么时候不要 strlcpy 和 strlcat ?

然而, strlcpy 和 strlcat 处理固定大小的缓冲区很好,但他们不能在所有情况下代理 strncpy 和 strncat 。有时候操作缓冲区并不是真正的 C字符串(例如,结构体 utmp 中的字符串)时候就是必要的。然而,我们争论的这样假冒字符串不应该用在新的编码中,由于他们可能被误用,并且据我们的经验,这也是 BUG 的根源。另外, strlcpy 和 strlcat 函数并不是 C 里面修正字符串处理的尝试,他们设计为来适应正常的 C 字符串框架。如果你需要字符串函数支持动态分配的、任意大小的缓冲区,你可能需要检查 asstring 包,在 MIB 软件中。

谁使用 strlcpy 和 strlcat ?

Strlcpy 和 strlcat 函数首先出现在 OpenBSD2.4 。这些函数最近被将来的 Solaris 版本中批准。第三方包也开始收集这些 API 。例如, rsync包现在使用 strlcpy 并且提供它自己的版本如果 OS 不支持的话。其他的操作系统和应用程序将来使用 strlcpy 和 strlcat 是我们的希望,并且它将某个时候接受标准。

下一步是什么?

Strlcpy 和 strlcat 的源码可以免费获得,并且 BSD 风格的 license 是 OpenBSD 操作系统的一部分。你可以通过匿名 ftp 从ftp.openbsd.org 下载代码和相关的手册;目录为 /pub/OpenBSD/src/lib/libc/string 。 strlcpy 和 strlcat 的源码在 strlcpy.c 和 strlcat.c中。也可以找到相应的文档。

时间: 2024-11-15 00:28:27

Strlcpy和strlcat-一致的、安全的字符串复制和串接的相关文章

Strlcpy和strlcat——一致的、安全的字符串拷贝和串接函数【转】

转自:http://blog.csdn.net/kailan818/article/details/6731772 英文原文: http://www.gratisoft.us/todd/papers/strlcpy.html 英文作者: Todd C. Miller,  Theo de Raadt 译者:林海枫 译本地址:http://blog.csdn.net/linyt/archive/2009/07/27/4383328.aspx 注:本译文版权由译者所拥有,欢迎转载,但请注明译者和原文,

c语言-C语言指针,字符串复制过程的问题

问题描述 C语言指针,字符串复制过程的问题 下面是字符串复制的代码,str1[]如果限定大小为10,则会溢出,结果是s2正常,s1输出为 u? 请问为什么是这个结果呢? #include #include int main(){ char *s1; char *s2; char str[] = {""How are you?""}; char str1[10]={}; s1 = str; s2 = str1; while ((*s2 = *s1) != ''){ s

cmd set函数-cmd如何完成字符串复制

问题描述 cmd如何完成字符串复制 我想得到一个字符串,类似123456789123456789123456789---重复,直到总长度达到1000!cmd中set函数可实现我记得,具体忘记了!不用循环什么的,别搞复杂了!求助 解决方案 cmd中的字符串怎么复制cmd的内容 解决方案二: set不是用来设置环境变量的么,我看应该写个批处理用循环然后echo

c语言 字符串 返回值-C语言 字符串复制 函数返回值问题

问题描述 C语言 字符串复制 函数返回值问题 函数是为了将一个字符串复制一部分到另一个字符串.麻烦看下我函数里边的注释,即我直接用string1做返回值,而不用string.返回的结果不一样.这里有个疑问,string2不是已经被存在string1里面了吗,为什么还要string? 代码如下: #include char *strncpy(char *, char *,int ); int main(void) { char string1[30]="Hello,Jim."; char

java实现字符串匹配求两个字符串的最大公共子串_java

本文实例讲述了java实现求两个字符串最大公共子串的方法.分享给大家供大家参考,具体如下: 最近在项目工作中有一个关于文本对比的需求,经过这段时间的学习,总结了这篇博客内容:求两个字符串的最大公共子串. 算法思想:基于图计算两字符串的公共子串.具体算法思想参照下图: 输入字符串S1:achmacmh    输入字符串S2:macham 第a步,是将字符串s1,s2分别按字节拆分,构成一个二维数组: 二维数组中的值如b所示,比如第一行第一列的值表示字符串s2和s1的第一个字节是否相等,若相等就是1

strlcpy 和 strlcat 源码

strlcpy.cpp size_t strlcpy( char *dst, const char *src, size_t siz ){    char*            d = dst;    const char*        s = src;    size_t            n = siz;    if (s == 0 || d == 0) return 0;    /* Copy as many bytes as will fit */    if (n != 0 &

Rolling Hash(Rabin-Karp 算法)匹配字符串与anagram串

该算法常用的场景 字符串中查找子串,字符串中查找anagram形式的子串问题. 关于字符串查找与匹配 字符串可以理解为字符数组.而字符可以被转换为整数,他们具体的值依赖于他们的编码方式(ASCII/Unicode).这意味着我们可以把字符串当成一个整形数组.找到一种方式将一组整形数字转化为一个数字,就能够使得我们借助一个预期的输入值来Hash字符串. 既然字符串被看成是数组而不是单个元素,比较两个字符串是否想到就没有比较两个数值来得简单直接.去检查A和B是否相等,我们不得不通过枚举所有的A和B的

DataTable转成字符串复制到txt文本的小例子_实用技巧

自己写了个DataTable转成字符串的方法 复制代码 代码如下: public static string DataTableToString(DataTable dt){string dtstring = "";for (int i = 0; i < dt.Columns.Count; i++){dtstring =dtstring+ dt.Columns[i].ColumnName + "\t";}dtstring =dtstring+ "\r

《C语言及程序设计》实践参考——字符串复制

返回:贺老师课程教学链接  实践要求 [项目3-字符串复制]下面的程序,将str1中除空格外的所有字符,复制到了str2中. #include <stdio.h> int main() { char str1[100]="I am a happy boy\'s daddy.",str2[100]; int i=0,j=0; while(str1[i]!='\0') { if(str1[i]!=' ') { str2[j]=str1[i]; j++; } i++; } str