Strlcpy和strlcat——一致的、安全的字串拷貝和串接函式【轉】

桃子紅了吶發表於2017-11-23

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


本文轉自張昺華-sky部落格園部落格,原文連結:http://www.cnblogs.com/sky-heaven/p/5667651.html,如需轉載請自行聯絡原作者


相關文章