c中不安全的函式
今天,編寫的程式仍然利用這些呼叫,因為從來沒有人教開發人員避免使用它們。某些人從各處獲得某個提示,但即使是優秀的開發人員也會被這弄糟。他們也許在危險函式的自變數上使用自己總結編寫的檢查,或者錯誤地推論出使用潛在危險的函式在某些特殊情況下是“安全”的。
第 一位公共敵人是 gets()。永遠不要使用 gets()。該函式從標準輸入讀入使用者輸入的一行文字,它在遇到 EOF 字元或換行字元之前,不會停止讀入文字。也就是:gets() 根本不執行邊界檢查。因此,使用 gets() 總是有可能使任何緩衝區溢位。作為一個替代方法,可以使用方法 fgets()。它可以做與 gets() 所做的同樣的事情,但它接受用來限制讀入字元數目的大小引數,因此,提供了一種防止緩衝區溢位的方法。例如,不要使用以下程式碼:
void main() { char buf[1024]; gets(buf); } |
而使用以下程式碼:
#define BUFSIZE 1024 void main() { char buf[BUFSIZE]; fgets(buf, BUFSIZE, stdin); } |
C 語言中一些標準函式很有可能使您陷入困境。但不是所有函式使用都不好。通常,利用這些函式之一需要任意輸入傳遞給該函式。這個列表包括:
- strcpy()
- strcat()
- sprintf()
- scanf()
- sscanf()
- fscanf()
- vfscanf()
- vsprintf
- vscanf()
- vsscanf()
- streadd()
- strecpy()
- strtrns()
壞訊息是我們推薦,如果有任何可能,避免使用這些函式。好訊息是,在大多數情況下,都有合理的替代方法。我們將仔細檢查它們中的每一個,所以可以看到什麼構成了它們的誤用,以及如何避免它。
strcpy()函式將源字串複製到緩衝區。沒有指定要複製字元的具體數目。複製字元的數目直接取決於源字串中的數目。如果源字串碰巧來自使用者輸入,且沒有專門限制其大小,則有可能會陷入大的麻煩中!
如果知道目的地緩衝區的大小,則可以新增明確的檢查:
if(strlen(src) >= dst_size) { /* Do something appropriate, such as throw an error. */ } else { strcpy(dst, src); |
完成同樣目的的更容易方式是使用 strncpy() 庫例程:
strncpy(dst, src, dst_size-1); dst[dst_size-1] = '\0'; /* Always do this to be safe! */ |
如果 src 比 dst 大,則該函式不會丟擲一個錯誤;當達到最大尺寸時,它只是停止複製字元。注意上面呼叫 strncpy() 中的 -1。如果 src 比 dst 長,則那給我們留有空間,將一個空字元放在 dst 陣列的末尾。
當然,可能使用 strcpy() 不會帶來任何潛在的安全性問題,正如在以下示例中所見:
strcpy(buf, "Hello!"); |
即使這個操作造成 buf 的溢位,但它只是對幾個字元這樣而已。由於我們靜態地知道那些字元是什麼,並且很明顯,由於沒有危害,所以這裡無須擔心 ― 當然,除非可以用其它方式覆蓋字串“Hello”所在的靜態儲存器。
確保 strcpy() 不會溢位的另一種方式是,在需要它時就分配空間,確保通過在源字串上呼叫 strlen() 來分配足夠的空間。例如:
dst = (char *)malloc(strlen(src)); strcpy(dst, src); |
strcat()函式非常類似於 strcpy(),除了它可以將一個字串合併到緩衝區末尾。它也有一個類似的、更安全的替代方法 strncat()。如果可能,使用 strncat() 而不要使用 strcat()。
函式 sprintf()和 vsprintf()是用來格式化文字和將其存入緩衝區的通用函式。它們可以用直接的方式模仿 strcpy() 行為。換句話說,使用 sprintf() 和 vsprintf() 與使用 strcpy() 一樣,都很容易對程式造成緩衝區溢位。例如,考慮以下程式碼:
void main(int argc, char **argv) { char usage[1024]; sprintf(usage, "USAGE: %s -f flag [arg1]\n", argv[0]); } |
我們經常會看到類似上面的程式碼。它看起來沒有什 麼危害。它建立一個知道如何呼叫該程式字串。那樣,可以更改二進位制的名稱,該程式的輸出將自動反映這個更改。 雖然如此, 該程式碼有嚴重的問題。檔案系統傾向於將任何檔案的名稱限制於特定數目的字元。那麼,您應該認為如果您的緩衝區足夠大,可以處理可能的最長名稱,您的程式會 安全,對嗎?只要將 1024 改為對我們的作業系統適合的任何數目,就好了嗎?但是,不是這樣的。通過編寫我們自己的小程式來推翻上面所說的,可能容易地推翻這個限制:
void main() { execl("/path/to/above/program", <<insert really long string here>>, NULL); } |
函式 execl() 啟動第一個引數中命名的程式。第二個引數作為 argv[0] 傳遞給被呼叫的程式。我們可以使那個字串要多長有多長!
那麼如何解決 {v}sprintf() 帶來得問題呢?遺憾的是,沒有完全可移植的方法。某些體系結構提供了 snprintf() 方法,即允許程式設計師指定將多少字元從每個源複製到緩衝區中。例如,如果我們的系統上有 snprintf,則可以修正一個示例成為:
void main(int argc, char **argv) { char usage[1024]; char format_string = "USAGE: %s -f flag [arg1]\n"; snprintf(usage, format_string, argv[0], 1024-strlen(format_string) + 1); } |
注意,在第四個變數之前,snprintf() 與 sprintf() 是一樣的。第四個變數指定了從第三個變數中應被複制到緩衝區的字元最大數目。注意,1024 是錯誤的數目!我們必須確保要複製到緩衝區使用的字串總長不超過緩衝區的大小。所以,必須考慮一個空字元,加上所有格式字串中的這些字元,再減去格式 說明符 %s。該數字結果為 1000, 但上面的程式碼是更具有可維護性,因為如果格式字串偶然發生變化,它不會出錯。
{v}sprintf() 的許多(但不是全部)版本帶有使用這兩個函式的更安全的方法。可以指定格式字串本身每個自變數的精度。例如,另一種修正上面有問題的 sprintf() 的方法是:
void main(int argc, char **argv) { char usage[1024]; sprintf(usage, "USAGE: %.1000s -f flag [arg1]\n", argv[0]); } |
注意,百分號後與 s 前的 .1000。該語法表明,從相關變數(本例中是 argv[0])複製的字元不超過 1000 個。
如果任一解決方案在您的程式必須執行的系統上行不通,則最佳的解決方案是將 snprintf() 的工作版本與您的程式碼放置在一個包中。可以找到以 sh 歸檔格式的、自由使用的版本;請參閱參考資料。
繼續, scanf系列的函式也設計得很差。在這種情況下,目的地緩衝區會發生溢位。考慮以下程式碼:
void main(int argc, char **argv) { char buf[256]; sscanf(argv[0], "%s", &buf); } |
如果輸入的字大於 buf 的大小,則有溢位的情況。幸運的是,有一種簡便的方法可以解決這個問題。考慮以下程式碼,它沒有安全性方面的薄弱環節:
void main(int argc, char **argv) { char buf[256]; sscanf(argv[0], "%255s", &buf); } |
百分號和 s 之間的 255 指定了實際儲存在變數 buf 中來自 argv[0] 的字元不會超過 255 個。其餘匹配的字元將不會被複制。
接下來,我們討論 streadd()和 strecpy()。由於,不是每臺機器開始就有這些呼叫,那些有這些函式的程式設計師,在使用它們時,應該小心。這些函式可以將那些含有不可讀字元的字串轉換成可列印的表示。例如,考慮以下程式:
#include <libgen.h> void main(int argc, char **argv) { char buf[20]; streadd(buf, "\t\n", ""); printf(%s\n", buf); } |
該程式列印:
\t\n |
而不是列印所有空白。如果程式設計師沒有預料到需要 多大的輸出緩衝區來處理輸入緩衝區(不發生緩衝區溢位),則 streadd() 和 strecpy() 函式可能有問題。如果輸入緩衝區包含單一字元 ― 假設是 ASCII 001(control-A)― 則它將列印成四個字元“\001”。這是字串增長的最壞情況。如果沒有分配足夠的空間,以至於輸出緩衝區的大小總是輸入緩衝區大小的四倍,則可能發生緩 衝區溢位。
另一個較少使用的函式是 strtrns(),因為許多機器上沒有該函 數。函式 strtrns() 取三個字串和結果字串應該放在其內的一個緩衝區,作為其自變數。第一個字串必須複製到該緩衝區。一個字元被從第一個字串中複製到緩衝區,除非那個 字元出現在第二個字串中。如果出現的話,那麼會替換掉第三個字串中同一索引中的字元。這聽上去有點令人迷惑。讓我們看一下,將所有小寫字元轉換成大寫 字元的示例:
#include <libgen.h> void main(int argc, char **argv) { char lower[] = "abcdefghijklmnopqrstuvwxyz"; char upper[] = "ABCDEFGHIJKLMNOPQRSTUVWXYZ"; char *buf; if(argc < 2) { printf("USAGE: %s arg\n", argv[0]); exit(0); } buf = (char *)malloc(strlen(argv[1])); strtrns(argv[1], lower, upper, buf); printf("%s\n", buf); } |
以上程式碼實際上不包含緩衝區溢位。但如果我們使用了固定大小的靜態緩衝區,而不是用 malloc() 分配足夠空間來複制 argv[1],則可能會引起緩衝區溢位情況。
|
realpath() 函式接受可能包含相對路徑的字串,並將它轉換成指同一檔案的字串,但是通過絕對路徑。在做這件事時,它展開了所有符號連結。
該 函式取兩個自變數,第一個作為要規範化的字串,第二個作為將儲存結果的緩衝區。當然,需要確保結果緩衝區足夠大,以處理任何大小的路徑。分配的 MAXPATHLEN 緩衝區應該足夠大。然而,使用 realpath() 有另一個問題。如果傳遞給它的、要規範化的路徑大小大於 MAXPATHLEN,則 realpath() 實現內部的靜態緩衝區會溢位!雖然實際上沒有訪問溢位的緩衝區,但無論如何它會傷害您的。結果是,應該明確不使用 realpath(),除非確保檢查您試圖規範化的路徑長度不超過 MAXPATHLEN。
其它廣泛可用的呼叫也有類 似的問題。經常使用的 syslog() 呼叫也有類似的問題,直到不久前,才注意到這個問題並修正了它。大多數機器上已經糾正了這個問題,但您不應該依賴正確的行為。最好總是假定程式碼正執行在可 能最不友好的環境中,只是萬一在哪天它真的這樣。getopt() 系列呼叫的各種實現,以及 getpass() 函式,都可能產生內部靜態緩衝區溢位問題。如果您不得不使用這些函式,最佳解決方案是設定傳遞給這些函式的輸入長度的閾值。
自己模擬 gets() 的安全性問題以及所有問題是非常容易的。 例如,下面這段程式碼:
char buf[1024]; int i = 0; char ch; while((ch = getchar()) != '\n') { if(ch == -1) break; buf[i++] = ch; ------------------------------ </pre><ol type="1" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: decimal; "><span style="font-family: 宋體; font-size: 16px; "><strong>概述</strong></span></li></ol><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">在前面的安全編碼實踐的文章裡,我們討論了</span><span style="font-family: Calibri; line-height: 20px; ">GS</span><span style="font-family: 宋體; line-height: 20px; ">編譯選項,資料執行保護</span><span style="font-family: Calibri; line-height: 20px; ">DEP</span><span style="font-family: 宋體; line-height: 20px; ">功能,以及靜態程式碼分析工具</span><span style="font-family: Calibri; line-height: 20px; ">Prefast</span><span style="font-family: 宋體; line-height: 20px; ">。這裡,我們討論在</span><span style="font-family: Calibri; line-height: 20px; ">C/C++</span><span style="font-family: 宋體; line-height: 20px; ">程式碼中禁用危險的</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋體; line-height: 20px; ">,其主要目的是為了減少程式碼中引入安全漏洞的可能性。</span></p><ol type="1" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: decimal; "><span style="font-family: 宋體; font-size: 16px; "><strong>那些是危險的</strong></span><span style="font-family: 'Times New Roman'; font-size: 16px; "><strong>API</strong></span></li></ol><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>2.1</strong></span><span style="font-family: 宋體; line-height: 24px; font-size: 16px; "><strong>歷史</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">在微軟產品的安全漏洞中,有很大一部分是由於不正確的使用</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋體; line-height: 20px; ">動態庫(</span><span style="font-family: Calibri; line-height: 20px; ">C Runtime Library</span><span style="font-family: 宋體; line-height: 20px; ">)</span><span style="font-family: Calibri; line-height: 20px; "> </span><span style="font-family: 宋體; line-height: 20px; ">的函式,特別是有關字串處理的函式導致的。表一給出了微軟若干由於不當使用</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋體; line-height: 20px; ">動態庫函式而導致的安全漏洞【</span><span style="font-family: Calibri; line-height: 20px; ">1</span><span style="font-family: 宋體; line-height: 20px; ">,</span><span style="font-family: Calibri; line-height: 20px; ">p242</span><span style="font-family: 宋體; line-height: 20px; ">】。</span></p><a target=_blank name="0.1_table01" style="color: rgb(51, 102, 153); "></a><div><table border="2" cellspacing="0" width="638"><tbody><tr valign="top"><td style="text-align: left; "><span style="font-family: 宋體; ">微軟安全公告</span></td><td style="text-align: left; "><span style="font-family: 宋體; ">涉及產品</span></td><td style="text-align: left; "><span style="font-family: 宋體; ">涉及的函式</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS02-039</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft SQL Server 2000</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">sprint</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS05-010</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft License Server</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">lstrcpy</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS04-011</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (DCPromo)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">wvsprintf</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS04-011</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (MSGina)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">lstrcpy</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS04-031</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (NetDDE)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">wcscat</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: Calibri; ">MS03-045</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">Microsoft Windows (USER)</span></td><td style="text-align: left; "><span style="font-family: Calibri; ">wcscpy</span></td></tr></tbody></table></div><p align="center" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 黑體; font-size: 10px; line-height: 15px; ">表</span><span style="font-family: 'Times New Roman'; font-size: 10px; line-height: 15px; ">1</span><span style="font-family: 黑體; font-size: 10px; line-height: 15px; ">:不當使用</span><span style="font-family: 'Times New Roman'; font-size: 10px; line-height: 15px; ">C</span><span style="font-family: 黑體; font-size: 10px; line-height: 15px; ">動態庫函式而導致的安全漏洞</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">不當使用</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋體; line-height: 20px; ">動態庫函式容易引入安全漏洞,這一點並不奇怪。</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋體; line-height: 20px; ">動態庫函式的設計大約是</span><span style="font-family: Calibri; line-height: 20px; ">30</span><span style="font-family: 宋體; line-height: 20px; ">年前的事情了。當時,安全方面的考慮並不是設計上需要太多注意的地方。</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>2.2 </strong></span><span style="font-family: 宋體; line-height: 24px; font-size: 16px; "><strong>危險</strong></span><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>API</strong></span><span style="font-family: 宋體; line-height: 24px; font-size: 16px; "><strong>的列表</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">有關完整的危險</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋體; line-height: 20px; ">的禁用列表,大家可以參見</span><a target=_blank href="http://msdn.microsoft.com/en-us/library/bb288454.aspx" target="_blank" style="color: rgb(51, 102, 153); text-decoration: none; "><span style="font-family: Calibri; line-height: 20px; "><span style="text-decoration: underline; ">http://msdn.microsoft.com/en-us/library/bb288454.aspx</span></span></a><span style="font-family: Calibri; line-height: 20px; ">.</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">在這裡我們列出其中的一部分,以便大家對那些</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋體; line-height: 20px; ">被禁用有所體會。</span></p><a target=_blank name="0.1_table02" style="color: rgb(51, 102, 153); "></a><div><table border="2" cellspacing="0" width="632"><tbody><tr valign="top"><td style="text-align: left; "><span style="font-family: 宋體; ">禁用的</span><span style="font-family: Calibri; ">API</span></td><td style="text-align: left; "><span style="font-family: 宋體; ">替代的</span><span style="font-family: Calibri; ">StrSafe</span><span style="font-family: 宋體; ">函式</span></td><td style="text-align: left; "><span style="font-family: 宋體; ">替代的</span><span style="font-family: Calibri; ">Safe CRT</span><span style="font-family: 宋體; ">函式</span></td></tr><tr valign="top"><td colspan="3" style="text-align: left; "><span style="font-family: 宋體; ">有關字串拷貝的</span><span style="font-family: Calibri; ">API</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">strcpy, wcscpy, _tcscpy, _mbscpy, StrCpy, StrCpyA, StrCpyW, lstrcpy, lstrcpyA, lstrcpyW, strcpyA, strcpyW, _tccpy, _mbccpy</span></td><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">StringCchCopy, StringCbCopy,</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">StringCchCopyEx, StringCbCopyEx</span></p></td><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">strcpy_s</span></td></tr><tr valign="top"><td colspan="3" style="text-align: left; "><span style="font-family: 宋體; ">有關字串合併的</span><span style="font-family: Calibri; ">API</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">strcat, wcscat, _tcscat, _mbscat, StrCat, StrCatA, StrCatW, lstrcat, lstrcatA, lstrcatW, StrCatBuffW, StrCatBuff, StrCatBuffA, StrCatChainW, strcatA, strcatW, _tccat, _mbccat</span></td><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">StringCchCat, StringCbCat,</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">StringCchCatEx, StringCbCatEx</span></p></td><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">strcat_s</span></td></tr><tr valign="top"><td colspan="3" style="text-align: left; "><span style="font-family: 宋體; ">有關</span><span style="font-family: Calibri; ">sprintf</span><span style="font-family: 宋體; ">的</span><span style="font-family: Calibri; ">API</span></td></tr><tr valign="top"><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">wnsprintf, wnsprintfA, wnsprintfW, sprintfW, sprintfA, wsprintf, wsprintfW, wsprintfA, sprintf, swprintf, _stprintf</span></td><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">StringCchPrintf, StringCbPrintf,</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">StringCchPrintfEx, StringCbPrintfEx</span></p></td><td style="text-align: left; "><span style="font-family: 'Courier New'; font-size: 10px; ">_snprintf_s</span><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">_snwprintf_s</span></p></td></tr></tbody></table></div><p align="center" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 黑體; font-size: 10px; line-height: 15px; ">表</span><span style="font-family: 'Times New Roman'; font-size: 10px; line-height: 15px; ">2</span><span style="font-family: 黑體; font-size: 10px; line-height: 15px; ">:禁用</span><span style="font-family: 'Times New Roman'; font-size: 10px; line-height: 15px; ">API</span><span style="font-family: 黑體; font-size: 10px; line-height: 15px; ">的列表(部分)</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">其它被禁用的</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋體; line-height: 20px; ">還有</span><span style="font-family: Calibri; line-height: 20px; ">scanf, strtok, gets, itoa</span><span style="font-family: 宋體; line-height: 20px; ">等等。</span><span style="font-family: Calibri; line-height: 20px; "> ”n”</span><span style="font-family: 宋體; line-height: 20px; ">系列的字串處理函式,例如</span><span style="font-family: Calibri; line-height: 20px; ">strncpy</span><span style="font-family: 宋體; line-height: 20px; ">等,也在被禁用之列。</span></p><ol type="1" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: decimal; "><span style="font-family: 宋體; font-size: 16px; "><strong>如何替代被禁用的危險</strong></span><span style="font-family: 'Times New Roman'; font-size: 16px; "><strong>API</strong></span></li></ol><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">從上面的介紹可以看出絕大多數</span><span style="font-family: Calibri; line-height: 20px; ">C</span><span style="font-family: 宋體; line-height: 20px; ">動態庫中的字串處理函式都被禁用。那麼,如何在程式碼中替代這些危險的</span><span style="font-family: Calibri; line-height: 20px; ">API</span><span style="font-family: 宋體; line-height: 20px; ">呢?在表</span><span style="font-family: Calibri; line-height: 20px; ">2</span><span style="font-family: 宋體; line-height: 20px; ">裡,我們看到有兩種替代方案:</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: disc; "><ul type="disc" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: Calibri; ">StrSafe</span></li><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: Calibri; ">Safe CRT</span></li></ul></li></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">後面我們會討論這兩種方案的不同之處。這裡我們先說它們的共同點:提供更安全的字串處理功能。特別在以下幾個方面:</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; line-height: 23px; list-style-type: disc; "><ul type="disc" style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: 宋體; ">目標快取區的大小被顯式指明。</span></li><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: 宋體; ">動態校驗。</span></li><li style="margin: 0px; padding: 2px 0px; list-style-type: disc; "><span style="font-family: 宋體; ">返回程式碼。</span></li></ul></li></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">以</span><span style="font-family: Calibri; line-height: 20px; ">StringCchCopy</span><span style="font-family: 宋體; line-height: 20px; ">舉例。它的定義如下:</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">HRESULT StringCchCopy( </span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> LPTSTR pszDest,</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> size_t cchDest,</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> LPCTSTR pszSrc</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">);</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: Calibri; line-height: 20px; ">cchDest</span><span style="font-family: 宋體; line-height: 20px; ">指明目標快取區</span><span style="font-family: Calibri; line-height: 20px; ">pszDest</span><span style="font-family: 宋體; line-height: 20px; ">最多能容納字元的數目,其值必須在</span><span style="font-family: Calibri; line-height: 20px; ">1</span><span style="font-family: 宋體; line-height: 20px; ">和</span><span style="font-family: Calibri; line-height: 20px; ">STRSAFE_MAX_CCH</span><span style="font-family: 宋體; line-height: 20px; ">之間。</span><span style="font-family: Calibri; line-height: 20px; ">StringCchCopy</span><span style="font-family: 宋體; line-height: 20px; ">總是確保</span><span style="font-family: Calibri; line-height: 20px; ">pszDest</span><span style="font-family: 宋體; line-height: 20px; ">被拷貝的字串是以</span><span style="font-family: Calibri; line-height: 20px; ">NULL</span><span style="font-family: 宋體; line-height: 20px; ">結尾。並且提供以下的返回程式碼:</span><span style="font-family: Calibri; line-height: 20px; "> S_OK</span><span style="font-family: 宋體; line-height: 20px; ">,</span><span style="font-family: Calibri; line-height: 20px; ">STRSAFE_E_INVALID_PARAMETER</span><span style="font-family: 宋體; line-height: 20px; ">,和</span><span style="font-family: Calibri; line-height: 20px; ">STRSAFE_E_INSUFFICIENT_BUFFER</span><span style="font-family: 宋體; line-height: 20px; ">。這樣,採用</span><span style="font-family: Calibri; line-height: 20px; ">StringCchCopy</span><span style="font-family: 宋體; line-height: 20px; ">來替代被禁用的</span><span style="font-family: Calibri; line-height: 20px; ">strcpy</span><span style="font-family: 宋體; line-height: 20px; ">的話,就可以有效降低由於誤用字串拷貝而導致快取溢位的可能。</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>3.1</strong></span><span style="font-family: 宋體; line-height: 24px; font-size: 16px; "><strong>使用</strong></span><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>StrSafe</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px; ">非常簡單。在</span><span style="font-family: Calibri; line-height: 20px; ">C/C++</span><span style="font-family: 宋體; line-height: 20px; ">程式碼中加入以下的標頭檔案即可。</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">#include "strsafe.h"</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: Calibri; line-height: 20px; ">StrSafe.h</span><span style="font-family: 宋體; line-height: 20px; ">包含在</span><span style="font-family: Calibri; line-height: 20px; ">Windows Platform SDK</span><span style="font-family: 宋體; line-height: 20px; ">中。使用者可以通過在微軟的網站直接下載。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">下面給出一個使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px; ">的程式碼示例【</span><span style="font-family: Calibri; line-height: 20px; ">2</span><span style="font-family: 宋體; line-height: 20px; ">】。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">不安全的程式碼:</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">void UnsafeFunc(LPTSTR szPath,DWORD cchPath) {</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> TCHAR szCWD[MAX_PATH];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> strncpy(szPath, szCWD, cchPath);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> strncat(szPath, TEXT("\\"), cchPath);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> strncat(szPath, TEXT("desktop.ini"),cchPath);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">}</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">在以上程式碼裡存在著幾個問題:首先,沒有錯誤程式碼的校驗。更嚴重的是,在</span><span style="font-family: Calibri; line-height: 20px; ">strncat</span><span style="font-family: 宋體; line-height: 20px; ">中,</span><span style="font-family: Calibri; line-height: 20px; ">cchPath</span><span style="font-family: 宋體; line-height: 20px; ">是目標快取區可以存放字元的最大數目,而正確傳遞的引數應該是目標快取區剩餘的字元數目。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px; ">後的程式碼是</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">bool SaferFunc(LPTSTR szPath,DWORD cchPath) {</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> TCHAR szCWD[MAX_PATH];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> if (GetCurrentDirectory(ARRAYSIZE(szCWD), szCWD) &&</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> SUCCEEDED(StringCchCopy(szPath, cchPath, szCWD)) &&</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("\\"))) &&</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> SUCCEEDED(StringCchCat(szPath, cchPath, TEXT("desktop.ini")))) {</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> return true;</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> }</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> return false;</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">}</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>3.2</strong></span><span style="font-family: 宋體; line-height: 24px; font-size: 16px; "><strong>使用</strong></span><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>Safe CRT</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: Calibri; line-height: 20px; ">SafeCRT</span><span style="font-family: 宋體; line-height: 20px; ">自</span><span style="font-family: Calibri; line-height: 20px; ">Visual Studio 2005</span><span style="font-family: 宋體; line-height: 20px; ">起開始支援。當代碼中使用了禁用的危險的</span><span style="font-family: Calibri; line-height: 20px; ">CRT</span><span style="font-family: 宋體; line-height: 20px; ">函式,</span><span style="font-family: Calibri; line-height: 20px; ">Visual Studio 2005</span><span style="font-family: 宋體; line-height: 20px; ">編譯時會報告相應警告資訊,以提醒開發人員考慮將其替代為</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋體; line-height: 20px; ">中更為安全的函式。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">下面給出一個使用</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋體; line-height: 20px; ">的程式碼示例【</span><span style="font-family: Calibri; line-height: 20px; ">3</span><span style="font-family: 宋體; line-height: 20px; ">】。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">不安全的程式碼:</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">void UnsafeFunc (const wchar_t * src)</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">{</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> // Original</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> wchar_t dest[20];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> wcscpy(dest, src); // </span><span style="font-family: 宋體; font-size: 10px; line-height: 15px; ">編譯警告</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> wcscat(dest, L"..."); // </span><span style="font-family: 宋體; font-size: 10px; line-height: 15px; ">編譯警告</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">}</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">以上這段程式碼裡存在著明顯快取溢位的問題。</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">使用</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋體; line-height: 20px; ">後的程式碼是</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">errno_t SaferFunc(const wchar_t * src)</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">{</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> wchar_t dest[20];</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> </span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> errno_t err = wcscpy_s(dest, _countof(dest), src);</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">if (!err)</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; "> return err;</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">return wcscat_s(dest, _countof(dest), L"...");</span></p><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Courier New'; font-size: 10px; line-height: 15px; ">}</span></p><ul style="margin: 5px 0px 5px 35px; padding: 0px; list-style-type: none; "><p align="justify" style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>3.3 StrSafe </strong></span><span style="font-family: 宋體; line-height: 24px; font-size: 16px; "><strong>和</strong></span><span style="font-family: 'Times New Roman'; line-height: 24px; font-size: 16px; "><strong>Safe CRT</strong></span><span style="font-family: 宋體; line-height: 24px; font-size: 16px; "><strong>的對比</strong></span></p></ul><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">我們看到,</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px; ">和</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋體; line-height: 20px; ">存在功能重疊的地方。那麼什麼時候使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px; ">,什麼時候使用</span><span style="font-family: Calibri; line-height: 20px; ">SafeCRT</span><span style="font-family: 宋體; line-height: 20px; ">呢?</span></p><p style="margin-top: 1em; margin-bottom: 0.5em; padding-top: 0px; padding-bottom: 0px; "> <span style="font-family: 宋體; line-height: 20px; ">下面的表格【</span><span style="font-family: Calibri; line-height: 20px; ">1</span><span style="font-family: 宋體; line-height: 20px; ">,</span><span style="font-family: Calibri; line-height: 20px; ">p246</span><span style="font-family: 宋體; line-height: 20px; ">】裡列出了兩者之間的差異。採用何種方式應該根據具體情況而定。有時候也許只能採取其中一種方式:例如如果你的開發系統是</span><span style="font-family: Calibri; line-height: 20px; ">Visual Studio 2003</span><span style="font-family: 宋體; line-height: 20px; ">的話,就只能使用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px; ">。或者你的程式碼中有許多</span><span style="font-family: Calibri; line-height: 20px; ">itoa</span><span style="font-family: 宋體; line-height: 20px; ">的話,就考慮使用</span><span style="font-family: Calibri; line-height: 20px; ">Safe CRT</span><span style="font-family: 宋體; line-height: 20px; ">,因為</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px; ">中沒有提供簡單的替代方式。有時候也許兩者都可以。這種情況下,我個人是更喜歡採用</span><span style="font-family: Calibri; line-height: 20px; ">StrSafe</span><span style="font-family: 宋體; line-height: 20px |