原文出 處:http://www.cppblog.com/windcsn/archive/2006/10/05/13386.html
概要:
時下緩衝區溢出攻擊已經增加,越來越多的程序員使用帶有 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 軟件中[9]。
誰使用 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 中。也可以找到相應的文檔。
作者: Todd C. Miller
http://www.courtesan.com/todd/papers/strlcpy.html
``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.