strlcpy和strlcat——一致的、安全的字符串拷贝和串接函数
?
strlcpy?和?strlcat——?一致的、安全的字符串拷貝和串接函數
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?架構 | ? 函數 | ? 時間 (秒) |
| M?68k | Strcpy | 0.137 |
| M?68k | Strncpy | 0.464 |
| M?68k | Strlcpy | 0.14 |
| A?lpha | Strcpy | 0.018 |
| A?lpha | Strncpy | 0.10 |
| A?lpha | 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——一致的、安全的字符串拷贝和串接函数的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 寻找数组中的第二大数
- 下一篇: N皇后问题的两个最高效的算法