转自: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

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

  1. C语言字符串拷贝

    C语言字符串拷贝利用指针操作,要清楚知道指针的指向 代码如下: #include <stdio.h> #include <assert.h> #include <stri ...

  2. 字符串中的一些基本操作函数(c语言)

    其中很多函数返回的都是首地址,程序中只是将该地址后的内容全部输出来了...并没有作特殊处理输出地址...还有几个函数有点小bug. #include"iostream" #incl ...

  3. C++内存问题大集合(指针问题,以及字符串拷贝问题,确实挺危险的)

    作者:rendao.org,版权声明,转载必须征得同意. 内存越界,变量被篡改 memset时长度参数超出了数组长度,但memset当时并不会报错,而是操作了不应该操作的内存,导致变量被无端篡改 还可 ...

  4. 字符串拷贝函数strcpy写法_转

    Code highlighting produced by Actipro CodeHighlighter (freeware)http://www.CodeHighlighter.com/--> ...

  5. 把json格式对象转成可提交字符串格式,会过滤掉函数 {a: {b: 3}, b: [1], c: "d"} -> a.b=3&b[0]=1&c=d

    var json = { name: "任务名称" , scoreRule: "", score: "", // 如果规则表达式不为空,则默 ...

  6. 【C语言】字符串替换空格:实现一个函数,把字符串里的空格替换成“%20”

    //字符串替换空格:实现一个函数,把字符串里的空格替换成"%20" #include <stdio.h> #include <assert.h> void ...

  7. php 中文字符串首字母的获取函数

    这篇文章介绍了php 中文字符串首字母的获取函数,有需要的朋友可以参考一下 function chineseFirst($str) { $str= iconv("UTF-8",&q ...

  8. 三种java 去掉字符串中的重复字符函数

    三种java 去掉字符串中的重复字符函数 public static void main(string[] args) { system.out.println(removerepeatedchar( ...

  9. Com组件的内存分配和释放,CredentialProvider SHStrDup 字符串拷贝问题

    一.简单介绍 熟悉CredentialProvider的同学应该知道,他为一个Com组件,于是,在这里的内存分配(字符串拷贝)的一系列操作就要依照con的标准来. 二.Com组件的内存分配和释放 CO ...

随机推荐

  1. HDU 6386 Age of Moyu

    Problem Description Mr.Quin love fishes so much and Mr.Quin’s city has a nautical system,consisiting ...

  2. http与www服务精解

    TCP/IP 协议介绍 在介绍 HTTP 协议之前,先简单说一下TCP/IP协议的相关内容.TCP/IP协议是分层的,从底层至应用层分别为:物理层.链路层.网络层.传输层和应用层,如下图所示: 从应用 ...

  3. MySQL 5.7远程连接

    将/etc/mysql/my.cnf中的bind_address那一行注释掉或修改为"bind_address=0.0.0.0": bind_address并没有在/etc/mys ...

  4. Javascript Step by Step - 01

    基本数据类型 简单数值类型: undefined, null, boolean, number和string,共有5种 复合数据类型:object,array,function typeof操作符用来 ...

  5. Android 如何在xmL 里面动态设置padding

    如题,Android 如何在xmL 里面动态设置padding 有时候,你的布局加载完成之后,你findViewByid 找到控件,设置padding 会导致白条,布局闪动,那怎么办呢? 你是不是就想 ...

  6. VHDL语法入门学习第一篇

    1. 现在先遇到一个VHDL的语法问题,以前没用过VHDL,现在要去研究下,进程(PROCESS) 进程内部经常使用IF,WAIT,CASE或LOOP语句.PROCESS具有敏感信号列表(sensit ...

  7. 恢复误删除表黑科技之relay log大法

      Preface       In my previous blogs,I've demonstrated several mothods of how to rescue a dropped ta ...

  8. Freemarker 语法详解

    Freemarker 在线中文官方参考手册 Freemarker是一款模板引擎,是一种基于模版生成静态文件的通用工具,它是使用纯java编写的,一般用来生成HTML页面. Freemarker 生成静 ...

  9. python练习题及实现--文件处理、date日期

    练习题作者:Vamei 出处:http://www.cnblogs.com/vamei http://www.cnblogs.com/vamei/archive/2012/07/19/2600135. ...

  10. 【志银】MySQL命令总结

    ===0-MySQL密码设置===0.1-登入和进入MySQL数据库: 0.1.1-登入MySQL数据库:C:\Users\Administrator>mysql -u用户名 -hMySQL服务 ...