Strlcpy和strlcat

英文原文: 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);

1strlcpy()strlcat()ANSI C原型

 

 

普遍的誤解

最普遍的誤解莫過於認爲函數 strncpy() 總是產生以NUL結束的目標字符串。然而只有當源字符串的長度小於size參數時,這一論斷才爲真。當拷貝任意長的用戶輸入到固定大小的緩衝區,問題就出現了。這種情況下,使用strncpy()最安全的方法是先將目標字符串的大小減1,再傳遞給strncpysize參數,然後手工給目標字符串加上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中的平均運行時間是在使用25Mhz68040CPU的機器HP9000/425tOpenBSD 2.5操作系統下和使用166Mhzalpha CPU的機器DEC AXPPCI166OpenBSD 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協議。你同樣可通過匿名ftpftp.openbsd.org/pub/OpenBSD/src/lib/libc/string目錄下載代碼和它的手冊。strlcpy()strlcat()的源代碼分別在文件strlcpy.cstrlcat.c中。文檔(使用tmac.doc troff宏)可從strlcpy.3中找到。

 

作者信息

 

1993 年, Todd C. Miller接管sudo軟件包的維護工作,並從此參加免費軟件社區。他作爲活躍的開發者加入OpenBSD項目。Todd1997年獲得姍姍來遲的科羅拉多州大學計算機科學專業學士學位。可以使用郵件地址[email protected]與他聯繫。

 

Theo de Raadt1990年起加入免費Unix操作系統。他早期的開發工作包括移植Minixsun3/50amiga,以及移植PDP-11 BSD 2.968030計算機。作爲NetBSD項目的創始人之一,Theo的工作內容爲維護和改進很多系統部件,包括sparc端口和免費的YP實現,這一實現被大多數免費系統使用。Theo1995年建立OpenBSD項目,項目集中(??)在安全,集成加密系統和代碼正確性等方面。Theo全職工作於提升OpenBSD項目。可通過郵件地址[email protected]與他聯繫。

 

參考資料

[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

發佈了1 篇原創文章 · 獲贊 20 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章