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

注:本译文版权由译者所拥有,欢迎转载,但请注明译者和原文,请匆用于任何商业用途。

 

 

 

Strlcpystrlcat——一致的、安全的字符串拷贝和串接函数

Todd C. Miller

University of Colorado, Boulder

Theo de Raadt

OpenBSD project

 

概述

 

随着流行的缓冲区溢出攻击的增加,越来越多程序员开始使用带有大小,即有长度限制的字符串函数,如strncpy() 和strncat() 。尽管这种趋势令人十分鼓舞,但通常的标准C 字符串函数并不是专为此而设计的。本文介绍另一种直观的,一致的,天生安全的字符串拷贝API 。

当函数 strncpy()和 strncat()作为 strcpy()和 strcat()的安全版本来使用时,仍然存在一些安全隐患。首先,这两函数以不同的,非直观的方式来处理NUL 结束符和长度参数,即使有经验的程序员也会混淆。其次,发生字符串截断时,也不容易检查。最后,strncpy() 函数使用0 来填充剩余的目标字符串空间,以招致性能下降。在所有这些问题之中,由长度参数引起的混淆以及与NUL结束符相关的问题最严重。在审核OpenBSD 源代码树的潜在安全漏洞时,我们发现strncpy() 和strncat() 猖獗误用的情况。尽管并非所有的误用都会导致可被利用的安全漏洞,但清楚地表明使用strncpy() 和strncat() 来实施安全的字符串操作这一准则已普遍受到误解。两个替代函数strlcpy() 和strlcat() 被提议通过提出一个字符串拷贝安全的API 来解决这些问题(参阅图1 函数原型)。这两函数保证产生包含NUL 的字符串,以长度即字符串按占用字节的数量作为入口参数,并且提供简便的方式来检查是否有字符串截断。两者均不会清零未使用的目标空间。

 

引言

 

1996 年年中,笔者和OpenBSD 项目的其它成员一起担任审核OpenBSD 源代码树的工作,以寻找安全问题,并强调缓冲区溢出问题。缓冲区溢出问题[1]最近在论坛上如 BugTraq[2]获得广泛的关注,并且也被广泛利用。我们发现大量的溢出是由于使用sprintf(),strcpy() 和strcat() 而造成无长度界限的字符串拷贝,在循环里操纵字符串时没有显式检查字符串长度也是元凶之一。除此之外,我们也发现在很多场合下,程序员已使用strncpy() 和strncat() 进行安全的字符串操纵,但未能领会这些API 的精妙之处。

因此在审核代码时,我们发现不仅有必要去检查是否使用不安全的函数,如strcpy() 和strcat() ,同时也要检查是是否有函数strncpy() 和strcat() 的不正确使用。检查是否正确使用并非总是显而易见,特别是使用“静态”变量或使用由calloc() 分配的缓冲区时,这些缓冲区总是预先就填满了NUL 结束符。我们得到一个结论:需要十分安全的函数来替代strncpy() 和strncat() ,从根本上简化程序员的工作,同时也使代码审核变得更容易。

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

图 1: strlcpy()和 strlcat()的 ANSI C原型

 

 

普遍的误解

最普遍的误解莫过于认为函数 strncpy() 总是产生以NUL 结束的目标字符串。然而只有当源字符串的长度小于size 参数时,这一论断才为真。当拷贝任意长的用户输入到固定大小的缓冲区,问题就出现了。这种情况下,使用strncpy() 最安全的方法是先将目标字符串的大小减1 ,再传递给strncpy 的size 参数,然后手工给目标字符串加上NUL 结束符。这样可以保证目标字符串总是以NUL 结尾的。严格地说,如果字符串是“静态”变量或者由calloc() 分配的变量,完全没有必要手工给字符串加上NUL 结束符。因为这些字符串在分配时已经清零了。然而,依赖这一特性通常会给后来维护代码的人造成混乱。

另一个误解认为把代码中的 strcpy() 和strcat() 换成strncpy() 和strncat() 所引起的性能下降微不足道。对于strncat() 来说,确实如此 。但对于 strncpy()来说则不是这样,因为它会把那些未用来存储字符串的字节清零。当目标字符串的大小远远大于源字符的长度时,这会导致为数不少[**] 的性能下降。Strncpy() 的行为因CPU 架构和它的实现而异,因此它所带来的性能下降也因它的行为而不同。

使用 strncat()最普遍的错误是使用不正确的 size参数。确实要保证 strncat()使目标字符串包含 NULL结束符,参数 size决不能把NULL字符的空间计算在内。最重要的是,参数 size不是目标字符串本身的大小,而是为字符串预留的空间的数量。由于参数 size 几乎总一个计算量,而非一个已知的常量,因此经常被错误地计算。

 

Strlcpy()strlcat()是如何简化编程的?

 

Strlcpy() 和 strlcat()函数提供一个一致的,绝无 二 义的 API ,帮助程序员编写更安全的防弹代码。首先,同时也是最重的,strlcpy() 和strlcat() 两者保证所有的目标字符串都以NUL 字符结尾,只要提供的size 参数为非零。其次,两个函数都把size参数作为整个目标字符的大小。大多情况下,它的值很容易在编译时通过使用sizeof 运算符来计算。最后,strlcpy() 和strlcat()均不给目标字符串清零未使用的字节(而是使用NUL 来表示字符串的结束)。

Strlcpy() 和 strlcat()函数返回他们尝试创建的字符串的长度。对于 strlcpy()来说,就是源字符串的长度;而对 strlcat()来说,就是目标字符串的长度(串接前的长度)加上源字符串的长度。对于检查是否发生字符截断,程序员只需要验证回返值是否不小于size参数。因此,就算发生截断,存储整个字符串所需的字节数现已知道,程序员可以分配一个更大的空间,接着重新拷贝字符串(如果需要的话)。返回值在语义上与snprintf() 的返回值类似,snprintf() 由BSD 实现并由即将来临的C9X 标准规范化(请注意,非并当前所有的snprintf 实现都遵循C9X )。如果没有发生截断,程序员现在也获知了结果字符串的长度。由于通常的实践是使用strncpy()和strncat() 来构建字符串,然后使用strlen() 来获得结果字符串的长度,因此(strlcpy() 和strlcat() )这一返回值语义非常有用。有了strlcpy() 和strlcat() 后,就不再需要最后一步的strlen() 来获得字符串的长度了。

示例1a 是有潜在缓冲区溢出的代码段(HOME 环境变量由用户所控制,可为任意长)。

 

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

示例 1a: 使用strcpy() 和strcat() 的代码段

示例 1b是同样功能的代码段,不过换成了 安全 地使用 strncpy() 和strncat()( 请注意我们不得已手工给目标字符串设置NUL 字符)。

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

示例1b: 转换成使用strncpy() 和strncat()

示例 1c是使用 strlcpy()/strlcat()API的 平凡 版本。它的优点是与示例 1a 一样简洁,但不需要利用新API 的返回值。

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

示例 1c: 使用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);

 

示列1d  检测是否截断

 

 

设计决策

 

在考虑strlcpy()和strlcat()应具有什么语义的时候,涌现出各种各样的想法。原先的想法是使strlcpy()和strlcat()的语义和strncpy()与strncat()的相同,唯一例外是 他们总是确保目标字符串以NUL 结尾。然而,回顾strncat()的普遍使用情况(和误用),我们深信strlcat()的size参数应该是整个字符串空间的大小,而不仅是剩下来未分配的字符数。起决定初返回值为拷贝字符的数目,???。很快我们决定返回值和snprintf()的具有相同的语义是这一个更好的选择,因为这样给予程序员最大的弹性去做截断检查和截断恢复。

 

性能

 

程序员现已开始避免使用strncpy()函数,原因是当目标缓冲区远远大于源字符串的长度时,该函数的性能欠佳。例如apache开发小组[6]以调用内部函数来取代strncpy(),并公布了性能上的提升[7]。同样地,ncurses [8]软件包最近删除了所有的strncpy()函数调用,结果tic工具的运行速度提高了四倍。我们谨希望,将来更多的程序员使用strlcpy()提供的接口,而非使用经定制的接口。

 

为获得在最糟糕情况下,strncpy()和strlcpy()差别的感性认识,我们运行一个测试程序,拷贝字符串“this is just a test”1000次到大小为1024字节的缓冲区。这对于strncpy()来说有点不公平,由于使用较短的字符串和较大的缓冲区,strncpy()必须为缓冲区大部分空间填充上NUL字符。然而在实践中,使用的缓冲区通常远远大于用户预期的输入。例如,路径名缓冲区的长度为MAXPATHLEN(1024字节) ,但大多数文件名远远小于这一长度。表1 中的平均运行时间是在使用25Mhz的68040CPU的机器HP9000/425t在OpenBSD 2.5操作系统下和使用166Mhz的alpha CPU的机器DEC AXPPCI166在OpenBSD 2.5操作系统下产生的结果。各种情况使用相同的C 函数版本,时间为time工具报告结果的“real time”部分。


 

CPU架构


 

函数


 

时间 (秒)


M68k


Strcpy


0.137


M68k


Strncpy


0.464


M68k


Strlcpy


0.14


Alpha


Strcpy


0.018


Alpha


Strncpy


0.10


Alpha


Strlcpy


0.02


Table 1: Performance timings in seconds

表1 :性能测时结果(秒)

 

从表 1 可以看到, strncpy()的计时结果远差于strncpy()和strlcpy()的结果。这可能不仅仅是因为填补NUL字符带来的开销,而且是因为CPU的数据缓存被长长的零串有效地刷新。

 

Strlcpy()strlcat()所不能及之处

 

尽管 strlcpy()和strlcat()善长于处理大小固定的缓冲区,但仍然不能完全取代strncpy()和strncat()。在某些情况下,必须操纵那些并非真正C 字符串的缓冲区(例如struct utmp中的字符串)。然而,我们认为这些“伪字符串”不应该使用在新的代码中,因为它们容易被误用,并且从我们的经验来说,这是bug的普遍源头。此外,strlcpy()和strlcat()函数并不尝试“修复”C 中的字符串处理。相反它们设计的初衷就是适合C 字符的标准架构。如果要使用支持动态分配,任意大小缓冲区的字符串函数,可以使用mib软件[9]里的”astring”包。

 

谁应该使用strlcpy()strlcat()?

 

Strlcpy()和strlcat()函数首先出现在OpenBSD 2.4中。最近两函数被同意纳入Solaris的新版中。第三方包也开始使用这一API。例如,rsync[5]软件包现在使用strlcpy(),如果OS不支持该函数则提供自己的版本。我们希望其它操作系统和应用程序以后会使用strlcpy()和strlcat(),而且希望经过若干时间会得到标准的接受。

 

下一步将是什么?

 

在 OpenBSD 项目中,我们计划使用strlcpy()和strlcat()替换每个strncpy()和strncat(),这是明智之举。即使OpenBSD中使用新API来编写新的代码,仍然有大量的代码在我们原先的安全审核过程中转换成strncpy()和strncat()。至今,我们继续在现有代码中发现由于错误使用strncpy()和strncat()而造成的bug。把旧代码更改为使用strlcpy()和strlcat(),应该能(??)一些程序提速,并且能(?)为一些程序揭开bug。

 

可从何处获得源代码?

 

Strlcpy()和strcat()的源代码可以免费获得,并遵循作为OpenBSD操作系统一部分的BSD协议。你同样可通过匿名ftp从ftp.openbsd.org的/pub/OpenBSD/src/lib/libc/string目录下载代码和它的手册。strlcpy()和strlcat()的源代码分别在文件strlcpy.c和strlcat.c中。文档(使用tmac.doc troff宏)可从strlcpy.3中找到。

 

作者信息

 

1993 年, Todd C. Miller接管sudo软件包的维护工作,并从此参加免费软件社区。他作为活跃的开发者加入OpenBSD项目。Todd于1997年获得姗姗来迟的科罗拉多州大学计算机科学专业学士学位。可以使用邮件地址Todd.Miller@cs.colorado.edu与他联系。

 

Theo de Raadt自1990年起加入免费Unix操作系统。他早期的开发工作包括移植Minix到sun3/50和amiga,以及移植PDP-11 BSD 2.9到68030计算机。作为NetBSD项目的创始人之一,Theo的工作内容为维护和改进很多系统部件,包括sparc端口和免费的YP实现,这一实现被大多数免费系统使用。Theo在1995年建立OpenBSD项目,项目集中(??)在安全,集成加密系统和代码正确性等方面。Theo全职工作于提升OpenBSD项目。可通过邮件地址deraadt@openbsd.org与他联系。

 

参考资料

[1] Aleph One. ``Smashing The Stack For Fun And Profit.''Phrack Magazine Volume Seven, Issue Forty-Nine.

[2] BugTraq Mailing List Archives. http://www.geek-girl.com/bugtraq/. This web page contains searchable archives of the BugTraq mailing list.

[3] Brian W. Kernighan, Dennis M. Ritchie.The C Programming Language, Second Edition.Prentice Hall, PTR, 1988.

[4] International Standards Organization. ``C9X FCD, Programming languages /*- C'' http://wwwold.dkuug.dk/jtc1/sc22/open/n2794/ This web page contains the current draft of the upcoming C9X standard.

[5] Andrew Tridgell, Paul Mackerras.The rsync algorithm.http://rsync.samba.org/rsync/tech_report/. This web page contains a technical report describing the rsync program.

[6] The Apache Group. The Apache Web Server. http://www.apache.org. This web page contains information on the Apache web server.

[7] The Apache Group. New features in Apache version 1.3. http://www.apache.org/docs/new_features_1_3.html. This web page contains new features in version 1.3 of the Apache web server.

[8] The Ncurses (new curses) home page. http://www.clark.net/pub/dickey/ncurses/. This web page contains Ncurses information and distributions.

[9] Forrest J. Cavalier III. ``Libmib allocated string functions.'' http://www.mibsoftware.com/libmib/astring/. This web page contains a description and implementation of a set of string functions that dynamically allocate memory as necessary.

 

 

转自:http://blog.csdn.net/linyt/article/details/4383328

时间: 2024-09-15 04:42:31

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

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

时下缓冲区溢出攻击已经增加,越来越多的程序员使用带有 size 或长度边界的字符串函数,例如: strncpy 和 strncat .这的确是一个趋势,但标准的 C 字符串函数并不是真正为这些任务而设计的.本文描述一个专门设计用于安全字符串复制的可选的.直觉的和一致的 API . 将 strncpy 和 strncat 作为 strcpy 和 strcat 安全版本有几个问题.两个函数都是以不同的和非直觉的方法来处理 NULL 结尾的和长度参数,即使有经验的程序员都有时迷惑:而检查什么时候发生截

Delphi中从字符串中提取单词及从字符串中提取汉字的函数

{从字符串中提取单词的函数} procedure StrToWordList(str: string; var List: TStringList); var p: PChar; i: Integer; begin if List = nil then List := TStringList.Create; List.Clear; {去除重复} List.Sorted := True; List.Duplicates := dupIgnore; p := PChar(str); {把单词以外的字

c语言-请教各位大神,实现用数组表示大整数及大整数与字符串相互转化的两个函数

问题描述 请教各位大神,实现用数组表示大整数及大整数与字符串相互转化的两个函数 怎么用数组表示大整数呢,大整数到底有多大,大整数怎么转化成字符串,c语言没有学好,对这些完全不懂啊 解决方案 字符数组实现两个大整数的加法用字符串表示大整数 解决方案二: 用char数组存大整数,比如你要存4564646874646465464646878797979871465465465,明显超过了long long的范围 那么此时就用数组存储了, char num[1000] = {0}; //声明一个数组,可

PHP实现删除字符串中任何字符的函数_php技巧

本文实例讲述了PHP实现删除字符串中任何字符的函数.分享给大家供大家参考.具体如下: function delStr($start, $end, $orgenStr) { //读取要删除字符位置的前一部分字符串,并赋值给$temp //strpos读取字符第一次出现的位置 //substr读取指定开始与结束位置的子字符串 //echo $before."-". $last; $temp=$orgenStr; while(strpos($temp, $start) &&

c++面试题字符串拷贝函数示例_C 语言

复制代码 代码如下: #include<iostream>using namespace std; //字符串拷贝函数char * sCpy(char *strDest, char *strSource){    _ASSERT((strDest != NULL) && (strSource!=NULL));    char *d = strDest;              //获取dest的当前位置    char *s = strSource;            /

完美的2个php检测字符串是否是utf-8编码函数分享_php实例

在php开发中有时候会用到转码函数,比如iconv(),mb_convert_encoding()函数,在用函数转码的时候或者解码的时候我们有时候需要先判断当前字符串编码类型,不如是否是utf-8编码,是的话然后进行编码转换等操作.下面是小编整理的目前web开发中网上使用率比较高的.好的php关于UTF-8编码的判断函数,代码如下: function is_utf8($string) //函数一 { // From http://w3.org/International/questions/qa

c++-编程实现两字符串的连接。要求使用字符数组保存字符串,不要使用系统函数。

问题描述 编程实现两字符串的连接.要求使用字符数组保存字符串,不要使用系统函数. 编程实现两字符串的连接.定义字符数组保存字符串,在程序中提示用户输入两个字符串,实现两个字符串的连接,最后用cout语句显示输出.用cin实现输入,注意,字符串的结束标志是ASCII码0,使用循环语句进行字符串间的字符拷贝. #include using namespace std; int main() { char a[500]; char b[500]; cin>>a; for(int i=0;i if(

c++中,已知函数名的字符串,怎么调用改函数?

问题描述 c++中,已知函数名的字符串,怎么调用改函数? 从文件中读取了一些要调用的函数的函数名.我想知道,怎么通过函数名的字符串调用这个函数. 解决方案 字符串不能直接转成函数调用吧, 少量的函数是不是可以用switch语句完成, switch(str) { case 填字符串: 字符串对应函数: break: }

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

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