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
注:本譯文版權由譯者所擁有,歡迎轉載,但請註明譯者和原文,請匆用於任何商業用途。
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架構 |
函式 |
時間 (秒) |
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
本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sky-heaven/p/5667651.html,如需轉載請自行聯絡原作者
相關文章
- [轉]delphi 有授權許可的字串拷貝函式原始碼字串函式原始碼
- 關於 js 物件 轉 字串 和 深拷貝 的 探討JS物件字串
- js的深拷貝和淺拷貝JS
- VUE 中 的深拷貝和淺拷貝Vue
- 對淺拷貝和深拷貝的理解
- C++拷貝建構函式(深拷貝,淺拷貝)C++函式
- 拷貝建構函式的作用函式
- C語言實現字串拷貝函式的幾種方法C語言字串函式
- js的淺拷貝和深拷貝和應用場景JS
- C++中建構函式,拷貝建構函式和賦值函式的詳解C++函式賦值
- js 陣列的淺拷貝和深拷貝JS陣列
- PHP中的淺拷貝和深拷貝薦PHP
- iOS深拷貝和淺拷貝iOS
- Java深拷貝和淺拷貝Java
- 物件深拷貝和淺拷貝物件
- JavaScript 深度拷貝和淺拷貝JavaScript
- JavaScript深拷貝和淺拷貝JavaScript
- js 淺拷貝和深拷貝JS
- js 深拷貝和淺拷貝JS
- JavaScript淺拷貝和深拷貝JavaScript
- js深拷貝和淺拷貝JS
- 拷貝建構函式函式
- 淺談Java中的淺拷貝和深拷貝Java
- 深拷貝和淺拷貝的簡要詳解
- 淺拷貝和深拷貝 iOS 的copy 以及 mutablecopyiOS
- 拷貝建構函式中的陷阱函式
- python 指標拷貝,淺拷貝和深拷貝Python指標
- C++淺拷貝和深拷貝C++
- go slice深拷貝和淺拷貝Go
- JavaScript之深拷貝和淺拷貝JavaScript
- 聊聊物件深拷貝和淺拷貝物件
- ECMAScript-淺拷貝和深拷貝
- js之淺拷貝和深拷貝JS
- 深度解析深拷貝和淺拷貝
- Objective C淺拷貝和深拷貝Object
- 談談我對深拷貝和淺拷貝的理解
- 陣列和物件的拷貝陣列物件
- 拷貝建構函式(比較全的)函式