strlcpy和strlcat——一致的、安全的字符串拷贝和串接函数
?
strlcpy?和?strlcat——?一致的、安全的字符串拷貝和串接函數(shù)
Todd C. Miller
University of Colorado, Boulder
Theo de Raadt
OpenBSD project
?
概述
?
隨著流行的緩沖區(qū)溢出攻擊的增加,越來(lái)越多程序員開(kāi)始使用帶有大小,即有長(zhǎng)度限制的字符串函數(shù),如?strncpy()?和?strncat()?。盡管這種趨勢(shì)令人十分鼓舞,但通常的標(biāo)準(zhǔn)?C?字符串函數(shù)并不是專(zhuān)為此而設(shè)計(jì)的。本文介紹另一種直觀的,一致的,天生安全的字符串拷貝?API?。?
當(dāng)函數(shù)?strncpy()?和?strncat()?作為?strcpy()?和?strcat()?的安全版本來(lái)使用時(shí),仍然存在一些安全隱患。首先,這兩函數(shù)以不同的,非直觀的方式來(lái)處理?NUL?結(jié)束符和長(zhǎng)度參數(shù),即使有經(jīng)驗(yàn)的程序員也會(huì)混淆。其次,發(fā)生字符串截?cái)鄷r(shí),也不容易檢查。最后,strncpy()?函數(shù)使用?0?來(lái)填充剩余的目標(biāo)字符串空間,以招致性能下降。在所有這些問(wèn)題之中,由長(zhǎng)度參數(shù)引起的混淆以及與?NUL?結(jié)束符相關(guān)的問(wèn)題最嚴(yán)重。在審核?OpenBSD?源代碼樹(shù)的潛在安全漏洞時(shí),我們發(fā)現(xiàn)?strncpy()?和?strncat()?猖獗誤用的情況。盡管并非所有的誤用都會(huì)導(dǎo)致可被利用的安全漏洞,但清楚地表明使用?strncpy()?和?strncat()?來(lái)實(shí)施安全的字符串操作這一準(zhǔn)則已普遍受到誤解。兩個(gè)替代函數(shù)?strlcpy()?和?strlcat()?被提議通過(guò)提出一個(gè)字符串拷貝安全的?API?來(lái)解決這些問(wèn)題(參閱圖?1?函數(shù)原型)。這兩函數(shù)保證產(chǎn)生包含?NUL?的字符串,以長(zhǎng)度即字符串按占用字節(jié)的數(shù)量作為入口參數(shù),并且提供簡(jiǎn)便的方式來(lái)檢查是否有字符串截?cái)唷烧呔粫?huì)清零未使用的目標(biāo)空間。
?
引言
?
1996?年年中,筆者和?OpenBSD?項(xiàng)目的其它成員一起擔(dān)任審核?OpenBSD?源代碼樹(shù)的工作,以尋找安全問(wèn)題,并強(qiáng)調(diào)緩沖區(qū)溢出問(wèn)題。緩沖區(qū)溢出問(wèn)題?[1]?最近在論壇上如?BugTraq?[2]?獲得廣泛的關(guān)注,并且也被廣泛利用。我們發(fā)現(xiàn)大量的溢出是由于使用?sprintf(),?strcpy()?和?strcat()?而造成無(wú)長(zhǎng)度界限的字符串拷貝,在循環(huán)里操縱字符串時(shí)沒(méi)有顯式檢查字符串長(zhǎng)度也是元兇之一。除此之外,我們也發(fā)現(xiàn)在很多場(chǎng)合下,程序員已使用?strncpy()?和?strncat()?進(jìn)行安全的字符串操縱,但未能領(lǐng)會(huì)這些?API?的精妙之處。?
因此在審核代碼時(shí),我們發(fā)現(xiàn)不僅有必要去檢查是否使用不安全的函數(shù),如?strcpy()?和?strcat()?,同時(shí)也要檢查是是否有函數(shù)strncpy()?和?strcat()?的不正確使用。檢查是否正確使用并非總是顯而易見(jiàn),特別是使用“靜態(tài)”變量或使用由?calloc()?分配的緩沖區(qū)時(shí),這些緩沖區(qū)總是預(yù)先就填滿了?NUL?結(jié)束符。我們得到一個(gè)結(jié)論:需要十分安全的函數(shù)來(lái)替代?strncpy()?和?strncat()?,從根本上簡(jiǎn)化程序員的工作,同時(shí)也使代碼審核變得更容易。
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?原型
?
?
普遍的誤解
最普遍的誤解莫過(guò)于認(rèn)為函數(shù)?strncpy()?總是產(chǎn)生以?NUL?結(jié)束的目標(biāo)字符串。然而只有當(dāng)源字符串的長(zhǎng)度小于?size?參數(shù)時(shí),這一論斷才為真。當(dāng)拷貝任意長(zhǎng)的用戶輸入到固定大小的緩沖區(qū),問(wèn)題就出現(xiàn)了。這種情況下,使用?strncpy()?最安全的方法是先將目標(biāo)字符串的大小減?1?,再傳遞給?strncpy?的?size?參數(shù),然后手工給目標(biāo)字符串加上?NUL?結(jié)束符。這樣可以保證目標(biāo)字符串總是以?NUL結(jié)尾的。嚴(yán)格地說(shuō),如果字符串是“靜態(tài)”變量或者由?calloc()?分配的變量,完全沒(méi)有必要手工給字符串加上?NUL?結(jié)束符。因?yàn)檫@些字符串在分配時(shí)已經(jīng)清零了。然而,依賴這一特性通常會(huì)給后來(lái)維護(hù)代碼的人造成混亂。
另一個(gè)誤解認(rèn)為把代碼中的?strcpy()?和?strcat()?換成?strncpy()?和?strncat()?所引起的性能下降微不足道。對(duì)于?strncat()?來(lái)說(shuō),?確實(shí)如此?。但對(duì)于?strncpy()?來(lái)說(shuō)則不是這樣,因?yàn)樗鼤?huì)把那些未用來(lái)存儲(chǔ)字符串的字節(jié)清零。當(dāng)目標(biāo)字符串的大小遠(yuǎn)遠(yuǎn)大于源字符的長(zhǎng)度時(shí),這會(huì)導(dǎo)致為數(shù)不少?[**]?的性能下降。?Strncpy()?的行為因?CPU?架構(gòu)和它的實(shí)現(xiàn)而異,因此它所帶來(lái)的性能下降也因它的行為而不同。
使用?strncat()?最普遍的錯(cuò)誤是使用不正確的?size?參數(shù)。確實(shí)要保證?strncat()?使目標(biāo)字符串包含?NULL?結(jié)束符,參數(shù)?size?決不能把?NULL?字符的空間計(jì)算在內(nèi)。最重要的是,參數(shù)?size?不是目標(biāo)字符串本身的大小,而是為字符串預(yù)留的空間的數(shù)量。由于參數(shù)size?幾乎總一個(gè)計(jì)算量,而非一個(gè)已知的常量,因此經(jīng)常被錯(cuò)誤地計(jì)算。
?
Strlcpy()?和?strlcat()?是如何簡(jiǎn)化編程的?
?
Strlcpy()?和?strlcat()?函數(shù)提供一個(gè)一致的,絕無(wú)?二?義的?API?,幫助程序員編寫(xiě)更安全的防彈代碼。首先,同時(shí)也是最重的,strlcpy()?和?strlcat()?兩者保證所有的目標(biāo)字符串都以?NUL?字符結(jié)尾,只要提供的?size?參數(shù)為非零。其次,兩個(gè)函數(shù)都把?size參數(shù)作為整個(gè)目標(biāo)字符的大小。大多情況下,它的值很容易在編譯時(shí)通過(guò)使用?sizeof?運(yùn)算符來(lái)計(jì)算。最后,?strlcpy()?和strlcat()?均不給目標(biāo)字符串清零未使用的字節(jié)(而是使用?NUL?來(lái)表示字符串的結(jié)束)。?
Strlcpy()?和?strlcat()?函數(shù)返回他們嘗試創(chuàng)建的字符串的長(zhǎng)度。對(duì)于?strlcpy()?來(lái)說(shuō),就是源字符串的長(zhǎng)度;而對(duì)?strlcat()?來(lái)說(shuō),就是目標(biāo)字符串的長(zhǎng)度(串接前的長(zhǎng)度)加上源字符串的長(zhǎng)度。對(duì)于檢查是否發(fā)生字符截?cái)?#xff0c;程序員只需要驗(yàn)證回返值是否不小于size?參數(shù)。因此,就算發(fā)生截?cái)?#xff0c;存儲(chǔ)整個(gè)字符串所需的字節(jié)數(shù)現(xiàn)已知道,程序員可以分配一個(gè)更大的空間,接著重新拷貝字符串(如果需要的話)。返回值在語(yǔ)義上與?snprintf()?的返回值類(lèi)似,?snprintf()?由?BSD?實(shí)現(xiàn)并由即將來(lái)臨的?C9X?標(biāo)準(zhǔn)規(guī)范化(請(qǐng)注意,非并當(dāng)前所有的?snprintf?實(shí)現(xiàn)都遵循?C9X?)。如果沒(méi)有發(fā)生截?cái)?#xff0c;程序員現(xiàn)在也獲知了結(jié)果字符串的長(zhǎng)度。由于通常的實(shí)踐是使用?strncpy()?和?strncat()?來(lái)構(gòu)建字符串,然后使用?strlen()?來(lái)獲得結(jié)果字符串的長(zhǎng)度,因此(?strlcpy()?和?strlcat()?)這一返回值語(yǔ)義非常有用。有了?strlcpy()?和?strlcat()?后,就不再需要最后一步的?strlen()?來(lái)獲得字符串的長(zhǎng)度了。?
示例?1a?是有潛在緩沖區(qū)溢出的代碼段(?HOME?環(huán)境變量由用戶所控制,可為任意長(zhǎng))。
strcpy(path, homedir);
strcat(path, "/");
strcat(path, ".foorc");
len = strlen(path);
示例?1a:?使用?strcpy()?和?strcat()?的代碼段
示例?1b?是同樣功能的代碼段,不過(guò)換成了?安全?地使用?strncpy()?和?strncat()(?請(qǐng)注意我們不得已手工給目標(biāo)字符串設(shè)置?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:?轉(zhuǎn)換成使用?strncpy()?和?strncat()
示例?1c?是使用?strlcpy()/strlcat()API?的?平凡?版本。它的優(yōu)點(diǎn)是與示例?1a?一樣簡(jiǎn)潔,但不需要利用新?API?的返回值。
strlcpy(path, homedir, sizeof(path));
strlcat(path, "/", sizeof(path));
strlcat(path, ".foorc", sizeof(path));
len = strlen(path);
示例?1c:?使用?strlcpy()/strlcat()?的平凡版本
由于示例?1c?是如此的容易閱讀和理解,故對(duì)它添加額外的檢查顯得格外簡(jiǎn)單。示例?1d?里檢查返回值以確定是否有足夠的空間來(lái)儲(chǔ)存源字符串。如果沒(méi)有足夠空間,返回一個(gè)錯(cuò)誤。雖然程序比以前有輕微的復(fù)雜,但更具魯棒性,同時(shí)避免最后一步的?strlen()?調(diào)用。
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?:?檢測(cè)是否截?cái)?/span>
?
?
設(shè)計(jì)決策
?
在考慮?strlcpy()?和?strlcat()?應(yīng)具有什么語(yǔ)義的時(shí)候,涌現(xiàn)出各種各樣的想法。原先的想法是使?strlcpy()?和?strlcat()?的語(yǔ)義和?strncpy()?與strncat()?的相同,唯一例外是?他們總是確保目標(biāo)字符串以?NUL?結(jié)尾。然而,回顧?strncat()?的普遍使用情況(和誤用),我們深信?strlcat()?的?size?參數(shù)應(yīng)該是整個(gè)字符串空間的大小,而不僅是剩下來(lái)未分配的字符數(shù)。起決定初返回值為拷貝字符的數(shù)目,???。很快我們決定返回值和?snprintf()?的具有相同的語(yǔ)義是這一個(gè)更好的選擇,因?yàn)檫@樣給予程序員最大的彈性去做截?cái)鄼z查和截?cái)嗷謴?fù)。
?
性能
?
程序員現(xiàn)已開(kāi)始避免使用?strncpy()?函數(shù),原因是當(dāng)目標(biāo)緩沖區(qū)遠(yuǎn)遠(yuǎn)大于源字符串的長(zhǎng)度時(shí),該函數(shù)的性能欠佳。例如?apache?開(kāi)發(fā)小組?[6]?以調(diào)用內(nèi)部函數(shù)來(lái)取代?strncpy()?,并公布了性能上的提升?[7]?。同樣地,?ncurses?[8]?軟件包最近刪除了所有的?strncpy()?函數(shù)調(diào)用,結(jié)果?tic?工具的運(yùn)行速度提高了四倍。我們謹(jǐn)希望,將來(lái)更多的程序員使用?strlcpy()?提供的接口,而非使用經(jīng)定制的接口。
?
為獲得在最糟糕情況下,?strncpy()?和?strlcpy()?差別的感性認(rèn)識(shí),我們運(yùn)行一個(gè)測(cè)試程序,拷貝字符串“?this is just a test”1000?次到大小為?1024?字節(jié)的緩沖區(qū)。這對(duì)于?strncpy()?來(lái)說(shuō)有點(diǎn)不公平,由于使用較短的字符串和較大的緩沖區(qū),?strncpy()?必須為緩沖區(qū)大部分空間填充上?NUL?字符。然而在實(shí)踐中,使用的緩沖區(qū)通常遠(yuǎn)遠(yuǎn)大于用戶預(yù)期的輸入。例如,路徑名緩沖區(qū)的長(zhǎng)度為?MAXPATHLEN(1024?字節(jié)?)?,但大多數(shù)文件名遠(yuǎn)遠(yuǎn)小于這一長(zhǎng)度。表?1?中的平均運(yùn)行時(shí)間是在使用?25Mhz?的?68040CPU?的機(jī)器?HP9000/425t?在?OpenBSD 2.5?操作系統(tǒng)下和使用?166Mhz?的?alpha CPU?的機(jī)器?DEC AXPPCI166?在?OpenBSD 2.5?操作系統(tǒng)下產(chǎn)生的結(jié)果。各種情況使用相同的?C?函數(shù)版本,時(shí)間為?time?工具報(bào)告結(jié)果的“?real time”?部分。
| ? CPU?架構(gòu) | ? 函數(shù) | ? 時(shí)間 (秒) |
| 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?:性能測(cè)時(shí)結(jié)果(秒) | ||
?
從表?1?可以看到,?strncpy()?的計(jì)時(shí)結(jié)果遠(yuǎn)差于?strncpy()?和?strlcpy()?的結(jié)果。這可能不僅僅是因?yàn)樘钛a(bǔ)?NUL?字符帶來(lái)的開(kāi)銷(xiāo),而且是因?yàn)?/span>?CPU?的數(shù)據(jù)緩存被長(zhǎng)長(zhǎng)的零串有效地刷新。
?
Strlcpy()?和?strlcat()?所不能及之處
?
盡管?strlcpy()?和?strlcat()?善長(zhǎng)于處理大小固定的緩沖區(qū),但仍然不能完全取代?strncpy()?和?strncat()?。在某些情況下,必須操縱那些并非真正?C?字符串的緩沖區(qū)(例如?struct utmp?中的字符串)。然而,我們認(rèn)為這些“偽字符串”不應(yīng)該使用在新的代碼中,因?yàn)樗鼈內(nèi)菀妆徽`用,并且從我們的經(jīng)驗(yàn)來(lái)說(shuō),這是bug?的普遍源頭。此外,?strlcpy()?和?strlcat()?函數(shù)并不嘗試“修復(fù)”?C?中的字符串處理。相反它們?cè)O(shè)計(jì)的初衷就是適合?C?字符的標(biāo)準(zhǔn)架構(gòu)。如果要使用支持動(dòng)態(tài)分配,任意大小緩沖區(qū)的字符串函數(shù),可以使用?mib?軟件?[9]?里的”?astring”?包。
?
誰(shuí)應(yīng)該使用?strlcpy()?和?strlcat()?
?
Strlcpy()?和?strlcat()?函數(shù)首先出現(xiàn)在?OpenBSD 2.4?中。最近兩函數(shù)被同意納入?Solaris?的新版中。第三方包也開(kāi)始使用這一?API?。例如,?rsync?[5]?軟件包現(xiàn)在使用?strlcpy()?,如果?OS?不支持該函數(shù)則提供自己的版本。我們希望其它操作系統(tǒng)和應(yīng)用程序以后會(huì)使用?strlcpy()?和?strlcat()?,而且希望經(jīng)過(guò)若干時(shí)間會(huì)得到標(biāo)準(zhǔn)的接受。
?
下一步將是什么?
?
在?OpenBSD?項(xiàng)目中,我們計(jì)劃使用?strlcpy()?和?strlcat()?替換每個(gè)?strncpy()?和?strncat()?,這是明智之舉。即使?OpenBSD?中使用新?API?來(lái)編寫(xiě)新的代碼,仍然有大量的代碼在我們?cè)鹊陌踩珜徍诉^(guò)程中轉(zhuǎn)換成?strncpy()?和?strncat()?。至今,我們繼續(xù)在現(xiàn)有代碼中發(fā)現(xiàn)由于錯(cuò)誤使用?strncpy()?和strncat()?而造成的?bug?。把舊代碼更改為使用?strlcpy()?和?strlcat()?,應(yīng)該能(??)一些程序提速,并且能?(?)?為一些程序揭開(kāi)?bug?。
?
可從何處獲得源代碼?
?
Strlcpy()?和?strcat()?的源代碼可以免費(fèi)獲得,并遵循作為?OpenBSD?操作系統(tǒng)一部分的?BSD?協(xié)議。你同樣可通過(guò)匿名?ftp?從?ftp.openbsd.org?的/pub/OpenBSD/src/lib/libc/string?目錄下載代碼和它的手冊(cè)。?strlcpy()?和?strlcat()?的源代碼分別在文件?strlcpy.c?和?strlcat.c?中。文檔(使用tmac.doc troff?宏)可從?strlcpy.3?中找到。
?
作者信息
?
1993?年,?Todd C. Miller?接管?sudo?軟件包的維護(hù)工作,并從此參加免費(fèi)軟件社區(qū)。他作為活躍的開(kāi)發(fā)者加入?OpenBSD?項(xiàng)目。?Todd?于?1997?年獲得姍姍來(lái)遲的科羅拉多州大學(xué)計(jì)算機(jī)科學(xué)專(zhuān)業(yè)學(xué)士學(xué)位??梢允褂绵]件地址?Todd.Miller@cs.colorado.edu?與他聯(lián)系。
?
Theo de Raadt?自?1990?年起加入免費(fèi)?Unix?操作系統(tǒng)。他早期的開(kāi)發(fā)工作包括移植?Minix?到?sun3/50?和?amiga?,以及移植?PDP-11 BSD 2.9?到?68030?計(jì)算機(jī)。作為?NetBSD?項(xiàng)目的創(chuàng)始人之一,?Theo?的工作內(nèi)容為維護(hù)和改進(jìn)很多系統(tǒng)部件,包括?sparc?端口和免費(fèi)的?YP?實(shí)現(xiàn),這一實(shí)現(xiàn)被大多數(shù)免費(fèi)系統(tǒng)使用。Theo?在?1995?年建立?OpenBSD?項(xiàng)目,項(xiàng)目集中(??)在安全,集成加密系統(tǒng)和代碼正確性等方面。?Theo?全職工作于提升?OpenBSD?項(xiàng)目??赏ㄟ^(guò)郵件地址deraadt@openbsd.org?與他聯(lián)系。
?
參考資料
[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.
轉(zhuǎn)自:http://blog.csdn.net/linyt/article/details/4383328
總結(jié)
以上是生活随笔為你收集整理的strlcpy和strlcat——一致的、安全的字符串拷贝和串接函数的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 寻找数组中的第二大数
- 下一篇: N皇后问题的两个最高效的算法