天天看點

串操作函數strlcpy和strlcat的安全使用 概要: 介紹 為什麼 strlcpy 和 strlcat 能夠安全 設計決策 性能 什麼時候不要 strlcpy 和 strlcat ? 誰使用 strlcpy 和 strlcat ? 下一步是什麼?

    已知strcpy和strcat容易引起buffer溢出,因而許多文檔推薦應當使用strncpy和strncat。但是這兩個函數如果使用不當,還是非常容易引發buffer溢出問題。

例如:

    char *strncpy(char *dest, const char *src, size_t n)中,strlen(src)應當小于n, 一定不能大于或等于n.

    char *strncat(char *dest, const char *src, size_t n)中, 目标緩沖區中剩餘的緩沖區的大小(即從@dest指針開始向後的容量)應當大于n, 一定不能小于或等于n.

    看到一篇文檔,推薦使用strlcpy和strlcat來替換strncpy和strncat,而且目前OpenBSD和Linux中已經逐漸在開始使用strlcpy和strlcat。

關于strlcpy和strlcat函數相關的英文文檔見[url]http://www.courtesan.com/todd/papers/strlcpy.html[/url],對應的中文翻譯見[url]http://www.cppblog.com/windcsn/archive/2006/10/05/13386.html[/url]

以下是該文檔的内容:

标題: Strlcpy和strlcat-一緻的、安全的字元串複制和串接

概要:

時下緩沖區溢出***已經增加,越來越多的程式員使用帶有 size 或長度邊界的字元串函數,例如: strncpy 和 strncat 。這的确是一個趨勢,但标準的 C 字元串函數并不是真正為這些任務而設計的。本文描述一個專門設計用于安全字元串複制的可選的、直覺的和一緻的 API 。 将 strncpy 和 strncat 作為 strcpy 和 strcat 安全版本有幾個問題。兩個函數都是以不同的和非直覺的方法來處理 NULL 結尾的和長度參數,即使有經驗的程式員都有時迷惑;而檢查什麼時候發生截斷也是不容易的。最後, strncpy 用 0 來填充目标字元串剩餘的部分,這是以損失性能為代價的。所有這些迷惑都是由長度參數引起的,空結束的要求也非常重要。當我們評估 OpenBSD 源樹的潛在安全漏洞的時候,我們發現大量濫用 strncpy 和 strncat 。當然,并不是所有的都導緻暴露的安全漏洞,上面的這些使說明了一點:使用 strncpy 和 strncat 作為安全字元串操作容易被誤解。推薦使用的函數是 strlcpy 和 strlcat ,通過為安全字元串設計的 API 來程式這些問題(見圖 1 的函數原型)。兩個函數都保證 NUL 結尾,長度參數是以位元組記數的,并且提供了檢查截斷的方法,兩個函數都不是在目标字元串中填充 0 。

介紹

在 1996 年中,作者和其他 OpenBSD 項目的成員一起承擔了對 OpenBSD 源樹的評估,為了找出安全問題;以緩沖區溢出作為開始。緩沖區溢出最近在一些論壇(例如 BugTraq )大量關注,并且正被廣泛地開拓。我們發現大量的緩沖區溢出是由于較大的使用 sprintf 、 strcpy 、 strcat 進行的字元串複制;在循環中操作字元串而沒有明确地檢查循環變量的長度也是一個問題。另外,我們也發現許多程式員使用 strncpy 和 strncat 來程序安全字元串操作但失敗的場景。 是以,在評估代碼的時候,我們發現不僅僅檢查 strcpy 和 strcat 的不安全使用,同樣也要檢查 strncpy 和 strncat 的不正确使用。檢查正确使用并不總是明顯地,特别在靜态變量和緩沖區或 calloc 配置設定的緩沖區,這些都容易被忽視。我們得到結論,一個安全的 strncpy 和 strncat 是必要的,首先可以減輕程式員的工作;另外也可以是代碼評估更容易。

size_t strlcpy(char *dst, const char *src, size_t size);

size_t strlcat(char *dst, const char *src, size_t size);

Figure 1:

ANSI C prototypes for strlcpy() and strlcat()

通常誤解 最通常的誤解是 strncpy 空結尾的目标字元串。然而,隻有源符串的長度小于 size 參數才是正确的。當使用者輸入的任意長度字元串時候,可能有問題。這種情況下最安全方法是傳遞一個小于目标字元串的 size 給 strncpy ,并且手動添加一個結束符号。這種方法下你可以總是保證目标字元穿是 NUL 結束的。嚴格地說,如果是一個靜态的字元串或一個 calloc 配置設定的字元串不必要手動添加一個結束符号;主要由于這些字元串在配置設定的時候是填充 0 的。然而這些特性時候比較迷惑的。 另外一個暗示的假定就是從 strcpy 到 strcat 代碼轉換到 strncpy 和 strncat 導緻的性能下降是可以接受的。對于 strncat 來說是正确的,但同樣對于 strncpy 來說并不正确,由于 strncpy 将剩餘的目标位元組填充 0 ,這在字元串比較大的時候可能導緻可觀的性能損失。确切的損失由 CPU 架構和實作的而決定。 最常見的錯誤是使用 strncat 和一個不正确的 size 參數。然而 strncat 保證目标字元串是 NULL 結尾的,你不需要在 size 參數中為 NUL 計算機空間。最重要的,這不是目标字元串自身的大小,而是可用空間的數量。是以這個值總是要計算,并且作為一個可靠的常量,它常常也不能正确計算。

為什麼 strlcpy 和 strlcat 能夠安全

這兩個函數提供了一個一緻的、沒有二義性的 API 來幫助程式員寫比較防彈代碼。首先也是最重要的,兩個函數都能夠保證所有的目标字元串是 NUL 結尾的,給定的 size 非 0 ;其次,兩個函數都将目标字元串的整個 size 作為一個 size 參數。在大多數情況下,這個值比較容易在編譯期間使用 sizeof 操作符号來計算;最後,不管是 strlcpy 還是 strlcat 都不 0 填充他們的目标字元串(而是強迫 NUL 到字元串的結尾)。 Strlcpy 和 strlcat 函數傳回最終建立的字元串長度。對于 strlcpy 來說是源的長度,對于 strlcat 來說意味着目标的長度加源的長度。為了檢查截斷,程式員需要驗證傳回值是否小于 size 參數。是以,如果發生截斷,可以發現已經存儲了多少個位元組,并且程式員可以重新配置設定空間來重新複制字元串。傳回值和 snprintf 在 BSD 上的實作有相同的含義。如果沒有截斷發生,程式員現在有傳回值長度的字元串;這是有用的,因為通常情況用 strncpy 和 strncat 來構造字元串并且使用 strlen 來取得字元串的長度。使用 strlcpy 和 strlcat , strlen 就不需要了。 例子 1a 是潛在緩沖區溢出的例子( HOME 環境變量由使用者來控制可以是任意長度)。 strcpy(path, homedir);

strcat(path, "/");

strcat(path, ".foorc");

len = strlen(path);

Example 1a:

Code fragment using strcpy() and strcat()

例子 1b 轉換到 strncpy 和 strncat 的同樣代碼片段(注意,我必須自己添加字元串結束符号)。 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);

Example 1b:

Converted to strncpy() and strncat()

例子 1c 是到 strlcpy/strlcat 的變化,其有例子 1a 一樣簡單的好處,但卻沒有利用 API 的傳回值: strlcpy(path, homedir, sizeof(path));

strlcat(path, "/", sizeof(path));

strlcat(path, ".foorc", sizeof(path));

len = strlen(path);

Example 1c:

Trivial conversion to 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);

Example 1d:

Now with a check for truncation

設計決策

許多思想加入判斷 strlcpy 和 strlcat 應該是什麼語義。最初的想法是使 strlcpy 和 strlcat 與 strncpy 和 strncat 相同,并且始終是 NUL 結束的目标字元串。然而,當我們回過來看常用(和誤用) strncat 說服我們 strlcat 的 size 參數應該是字元串的所有大小而不僅僅是未配置設定的字元的數量。傳回值開始作為複制字元串的數量,由于有複制和串聯的副作用。我們很快決定傳回值應該與 sprintf 一樣,這樣程式可以比較彈性的處理截斷和恢複。

性能

當目标字元串的長度比源字元串明顯大很多的時候,程式員正在避免使用 strncpy ,主要由于其降低性能。例如, Apache 組用内部函數來代替 strncpy 并且注意到性能提升。同樣, ncurses 包最近删除了 strncpy ,結果比 tic 實作提高了四倍。我們的希望是,将來更多的程式員使用 strlcpy 而不是自定義的接口。 為了對性能的降低有一個感覺,我們比較 strncpy 和 strlcpy ,并且複制字元串 ’’ ;也就是複制 1000 次到 1024 位元組的緩沖區中。這對 strncpy 有點不公平,由于使用了大的緩沖區和小的字元串,并且大緩沖區的時候, strncpy 不得不填充多餘的緩沖為 NUL 字元。實際上,通常使用的緩沖區都比使用者輸入的大,例如,路徑名稱緩沖區是 MAXPATHLEN 長( 1024 ),但多數檔案都比較短。表 1 中的平均運作時間在 HP9000/425t , 25Mhz68040 CPU 運作 OPENBSD2.5 , DEC AXPPCI166 上 166Mhz Alpha CPU 運作 OpenBSD 。所有的 case 都是相同 C 版本函數,時間由時間工具産生:

 cpu architecture   function   time (sec) 
m68k strcpy 0.137
m68k strncpy 0.464
m68k strlcpy 0.14
alpha strcpy 0.018
alpha strncpy 0.10
alpha strlcpy 0.02

表 1 :性能時間表 如我們在表 1 中看到的一樣, strncpy 的時間是最壞的。可能的原因不僅是 NUL 填充,也可能因為 CPU 資料緩沖區被長流 0flush 的原因。

什麼時候不要 strlcpy 和 strlcat ?

然而, strlcpy 和 strlcat 處理固定大小的緩沖區很好,但他們不能在所有情況下代理 strncpy 和 strncat 。有時候操作緩沖區并不是真正的 C 字元串(例如,結構體 utmp 中的字元串)時候就是必要的。然而,我們争論的這樣假冒字元串不應該用在新的編碼中,由于他們可能被誤用,并且據我們的經驗,這也是 BUG 的根源。另外, strlcpy 和 strlcat 函數并不是 C 裡面修正字元串處理的嘗試,他們設計為來适應正常的 C 字元串架構。如果你需要字元串函數支援動态配置設定的、任意大小的緩沖區,你可能需要檢查 asstring 包,在 MIB 軟體中。

誰使用 strlcpy 和 strlcat ?

Strlcpy 和 strlcat 函數首先出現在 OpenBSD2.4 。這些函數最近被将來的 Solaris 版本中準許。第三方包也開始收集這些 API 。例如, rsync 包現在使用 strlcpy 并且提供它自己的版本如果 OS 不支援的話。其他的作業系統和應用程式将來使用 strlcpy 和 strlcat 是我們的希望,并且它将某個時候接受标準。

下一步是什麼?

Strlcpy 和 strlcat 的源碼可以免費獲得,并且 BSD 風格的 license 是 OpenBSD 作業系統的一部分。你可以通過匿名 ftp 從 ftp.openbsd.org 下載下傳代碼和相關的手冊;目錄為 /pub/OpenBSD/src/lib/libc/string 。 strlcpy 和 strlcat 的源碼在 strlcpy.c 和 strlcat.c 中。也可以找到相應的文檔。

更多參考資料:

[1] 安全程式設計: 防止緩沖區溢出, 防止如今最常見的程式缺陷, [url]http://www.ibm.com/developerworks/cn/linux/l-sp/part4/[/url]

轉載于:https://blog.51cto.com/kapok/112454

繼續閱讀