官术网_书友最值得收藏!

2.5.7 strncpy()和strncat()

strncpy()和strncat()函數(shù)與strcpy()和strcat()函數(shù)是類似的,但每個函數(shù)都有一個額外的size_t類型的參數(shù)n用于限制要被復制的字符數(shù)量。這些函數(shù)可以被認為是截斷型的復制和拼接函數(shù)。

strncpy()庫函數(shù)與strcpy()完成相似的功能,但前者允許指定一個最大大小n:


1  char *strncpy( 
2    char * restrict s1, const char * restrict s2, size_t n 
3  ); 

strncpy()函數(shù)的用法如下面的示例所示:


strncpy(dest, source, dest_size - 1); 
dest[dest_size - 1] = '\0'; 

因為strncpy()函數(shù)不能保證用空字符終止目標字符串,所以程序員必須小心,以確保目標字符串是正確地以空字符終止的,并且沒有覆蓋最后一個字符。

C標準的strncpy()函數(shù)經(jīng)常被推薦為strcpy()函數(shù)“更安全”的替代品。然而,strncpy()容易發(fā)生串終止錯誤,詳情見隨后介紹的“C11附錄K邊界檢查接口”

strncat()函數(shù)具有以下簽名:


1  char *strncat( 
2    char * restrict s1, const char * restrict s2, size_t n
3  );

strncat()函數(shù)從s2指向的數(shù)組追加不超過n個字符(空字符和它后面的字符不追加)到s1指向的字符串結尾。s2最初的字符覆蓋了s1末尾的空字符。終止空字符總是被附加到結果字符串。因此,在s1指向的數(shù)組中的最大字符數(shù)量是strlen(s1) + n +1。

必須謹慎使用strncpy()和strncat()函數(shù),或根本不使用它們,尤其是在有更不易出錯的替代品的時候。下面是一個實際代碼的例子,它是從現(xiàn)有的代碼中將strcpy()和strcat()簡單地變換成strncpy()和strncat()函數(shù)得到的:


strncpy(record, user, MAX_STRING_LEN - 1); 
strncat(record, cpw, MAX_STRING_LEN - 1); 

問題在于strncat()的最后一個參數(shù)不應該是緩沖區(qū)的總長度,而應該是調用strncpy()后緩沖區(qū)剩余的長度。這兩個函數(shù)都要求指定剩余的長度而不是緩沖區(qū)的總長度。由于剩余的長度在每次添加或刪除數(shù)據(jù)時都會改變,因此程序員必須跟蹤這些改變或重新計算剩余長度。這個過程很容易出錯,并且可能會導致漏洞。下面的調用在使用strncat()連接字符串之前正確地計算了剩余的空間:


strncat(dest, source, dest_size-strlen(dest)-1)

用strncpy()和strncat()作為strcpy()和strcat()函數(shù)替代品的另一個問題是,當結果字符串被截斷時,這兩個函數(shù)都沒有提供一個狀態(tài)代碼或報告。兩個函數(shù)都返回目標緩沖區(qū)的指針,這就要求程序員付出大量的勞動來確定結果字符串是否被截斷了。

strncpy()還存在一個性能問題,當源數(shù)據(jù)被復制完之后,它會將目標字符串填滿null字符。雖然對這種行為并沒有什么合理的解釋,但有很多程序已經(jīng)依賴于這個特性,因此很難變動這種行為。

利用strncpy()和strncat()函數(shù)作為strcpy()和strcat()的替代函數(shù),這種角色超出了它們的用途。這些函數(shù)的最初目的是允許復制和連接一個子字符串。然而,這些函數(shù)都容易發(fā)生緩沖區(qū)溢出和空字符終止錯誤。

C11附錄K邊界檢查接口。C11附錄K指定strncpy_s()和strncat_s()函數(shù)作為strncpy()和strncat()很接近的替代品。

strncpy_s()從源字符串中復制不超過指定數(shù)目的連續(xù)字符(空字符后面的字符將不被復制)到目標字符數(shù)組中。strncpy_s()函數(shù)具有以下簽名:


1  errno_t strncpy_s( 
2    char * restrict s1,
3    rsize_t s1max,
4    const char * restrict s2,
5    rsize_t n
6  );

strncpy_s()函數(shù)有一個額外的參數(shù)用于給出目標數(shù)組的大小,以防止緩沖區(qū)溢出。如果發(fā)生運行時約束違反,則目標數(shù)組被設置為空字符串,以增加問題的能見度。

以下兩個條件之一發(fā)生時,strncpy_s()函數(shù)停止從源字符串復制到目標數(shù)組:

1.源字符串的空終止字符被復制到目標字符串。

2.已復制由n參數(shù)指定的數(shù)量的字符。

如果空字符終結符不是從源字符串復制的,那么提供的目標結果會另外包括一個空字符終結符。操作的結果,包括空終結符,必須能夠容納在目標字符串中,否則就會發(fā)生運行時約束違反。該函數(shù)永遠不會修改目標數(shù)組外的存儲。

strncpy_s()函數(shù)返回0表示成功。如果輸入?yún)?shù)是無效的,它會返回一個非零值并把目標字符串設置為空字符串。如果源或目標指針為NULL,或者目標字符串的最大長度為0或大于RSIZE_MAX,則輸入驗證失敗。如果指定復制的字符數(shù)量大于RSIZE_MAX,則輸入同樣被認為是無效的。

一個strncpy_s()操作在其指定復制的字符數(shù)量超過目標字符串的最大長度時仍然可能執(zhí)行成功,前提是源字符串實際包含的字符數(shù)目小于目標字符串的最大長度。如果需要復制的字符數(shù)大于或等于目標字符串的最大長度并且源字符串比目標緩沖區(qū)長,則該操作將會失敗。

因為源字符串中的字符數(shù)目受限于n參數(shù),且目標字符串有一個單獨的參數(shù)用于給出目標數(shù)組元素的最大數(shù)量,所以strncpy_s()函數(shù)可以安全地復制子串,而不只是整個字符串或它的尾部。

由于意外的字符串截斷可能是一個安全漏洞,因此strncpy_s()不截斷源字符串(由空終結符和n參數(shù)分隔),以適應目標。截斷是一種違反運行時約束的動作。然而,有一個習慣用法,允許一個程序使用strncpy_s()函數(shù)來強制截斷。如果參數(shù)n是目標字符串大小減1,則strncpy_s()將復制整個源字符串至目標字符串或截斷它,以便它能在目標數(shù)組中容納下(一如既往的,結果將是以空字符結尾的)。例如,下面的調用將把src復制到dest數(shù)組,從而在dest中產(chǎn)生一個正確地以空字符結尾的字符串。當dest已滿(包括空終結符)或當src的所有內容都已被復制時,復制操作將停止。


strncpy_s(dest, sizeof dest, src, (sizeof dest)-1)

雖然OpenBSD的strlcpy()函數(shù)與strncpy()類似,但它更類似于strcpy_s(),而不是strncpy_s()。不像strlcpy(),strncpy_s()支持檢查運行時約束,如目標數(shù)組的大小,而且它不會截斷字符串。

因為必須指定目標緩沖區(qū)的大小和要追加的字符的最大數(shù)目,所以使用strncpy_s()函數(shù)是不太可能會引入安全漏洞的。考慮下面的定義:


1  char src1[100] = "hello"; 
2  char src2[7] = {'g','o','o','d','b','y','e'}; 
3  char dst1[6], dst2[5], dst3[5]; 
4  errno_t r1, r2, r3; 

因為目標字符數(shù)組有足夠的存儲空間,所以下面對strncpy_s()的調用將r1賦予0值并把序列hello\0賦值給dst1:


r1 = strncpy_s(dst1, sizeof(dst1), src1, sizeof(src1)); 

下面的調用給r2賦予0值并把序列“good\0”賦值給dst2:


r2 = strncpy_s(dst2, sizeof(dst2), src2, 4);

然而,沒有足夠的空間來把src1字符串復制到dst3。因此,如果以下對strncpy_s()的調用返回,r3被賦予一個非零值且dst3[0]被賦值'\0':

r3 = strncpy_s(dst3, sizeof(dst3), src1, sizeof(src1));

如果用strncpy()來代替strncpy_s(),目標數(shù)組dst3將不會正確地以空結尾。

strncat_s()函數(shù)從源字符串附加不超過指定數(shù)目的連續(xù)字符(空字符后面的字符將不會被復制)到目標字符數(shù)組中。源字符串的首字符會覆蓋目標數(shù)組原來結尾的空字符。如果沒有從源字符串復制空字符,則在附加后的字符串結尾寫入一個空字符。

strncat_s()函數(shù)具有以下簽名/原型。


1  errno_t strncat_s(
2    char * restrict s1,
3    rsize_t s1max,
4    const char * restrict s2,
5    rsize_t n
6  );

如果源或目標指針為NULL,或者目標緩沖區(qū)的最大長度等于0或大于RSIZE_MAX,則運行時約束違反發(fā)生且strncat_s()函數(shù)返回一個非零值。如果目標數(shù)組已滿或者沒有足夠的空間供完全附加源字符串,函數(shù)也將失敗。strncat_s()函數(shù)還保證目標字符串是空字符終止的。

strncat_s()函數(shù)有一個額外的參數(shù)用于給出目標數(shù)組的大小,以防止緩沖區(qū)溢出。在目標數(shù)組的原始字符串加上從源數(shù)組追加的新字符必須在目標數(shù)組中能容納下并以空字符終止,以避免運行時違反約束。如果發(fā)生運行時約束違反,則目標數(shù)組被設置為空字符串,以增加問題的能見度。

當以下兩個條件中的某個首先發(fā)生時,strncat_s()函數(shù)將停止把源字符串附加到目標數(shù)組中。

1.以空字符結尾的源字符串已被復制到目標數(shù)組。

2.由n參數(shù)指定的數(shù)量的字符已被復制。

如果空字符終結符不是從源字符串復制的,就會為目標數(shù)組另外提供一個空字符終結符。產(chǎn)生的結果,包括空終結符,必須能在目標數(shù)組中容納,否則就會發(fā)生運行時約束違反。strncat_s()函數(shù)永遠不會修改目標數(shù)組外的存儲。

因為源字符串的字符數(shù)目受限于n參數(shù),且有一個單獨的參數(shù)用于給出目標數(shù)組元素的最大數(shù)量,所以strncat_s()函數(shù)可以安全地追加一個子串而不只是整個字符串或它的尾部。

由于意外的字符串截斷可能是一個安全漏洞,因此strncat_s()不截斷源字符串(以空終結符和n參數(shù)指定的),以適應目標數(shù)組。截斷是一種運行時約束違反。然而,有一個習慣用法,允許一個程序使用strncat_s()函數(shù)來強制截斷。如果參數(shù)n為留在目標數(shù)組的元素數(shù)量減1,那么strncat_s()將整個源字符串追加至目的地或截斷它,以適應目標數(shù)組的大小(一如既往的,結果將以空字符結尾)。例如,下面的調用將把src追加到dest數(shù)組,從而在dest產(chǎn)生一個正確地以空字符結尾的字符串。當dest已滿(包括空終結符),或src的所有內容均已附加時,將停止拼接:


1  strncat_s( 
2    dest, 
3    sizeof dest, 
4    src, 
5    (sizeof dest) - strnlen_s(dest, sizeof dest) - 1 
6  ); 

雖然OpenBSD的strlcat()函數(shù)類似于strncat(),但它更類似于strcat_s(),而不是strncat_s()。不同于strlcat(),strncat_s()支持檢查運行時約束,如目標數(shù)組的大小,并且它不會截斷字符串。

如果錯誤地指定目標緩沖區(qū)的最大長度和要復制的字符數(shù)量,strncpy_s()和strncat_s()函數(shù)仍然能造成緩沖區(qū)溢出。

動態(tài)分配函數(shù)。ISO/IEC TR 24731-2[ISO/IEC TR 24731-2:2010]描述了strndup()函數(shù),它也可以用來作為strncpy()函數(shù)的另一個替代函數(shù)。ISO/IEC TR 24731-2沒有定義任何strncat()函數(shù)的替代函數(shù)。strndup()函數(shù)和strdup()函數(shù)的功能相當,在一個新的,看似使用malloc()分配的內存塊中復制提供的字符串,唯一的例外是strndup()函數(shù)最多復制n加1個字節(jié)到新分配的內存中,并用空字節(jié)終止新字符串。如果字符串的長度大于n,那么只復制n個字節(jié)。如果n大于字符串的長度,那么字符串中的所有字節(jié)都被復制到新的內存緩沖區(qū),包括用于終止的空字節(jié)。新創(chuàng)建的字符串將永遠是被正確地終止的。分配的字符串必須通過把返回的指針傳遞給free()來回收內存。

替代函數(shù)總結。表2.7總結了本節(jié)描述的截斷復制的一些替代函數(shù)。

表2.7 截斷復制函數(shù)

表2.8總結了本節(jié)描述的截斷拼接的一些替代的函數(shù)。TR 24731-2沒有定義替代的截斷拼接函數(shù)。

表2.8 截斷級聯(lián)函數(shù)

主站蜘蛛池模板: 北流市| 柳林县| 孟州市| 沧源| 嘉善县| 青川县| 湘西| 西宁市| 行唐县| 焉耆| 松潘县| 屏东县| 陇川县| 马尔康县| 宜章县| 永胜县| 宜宾县| 工布江达县| 海晏县| 安乡县| 凤阳县| 容城县| 德格县| 赣榆县| 江北区| 榆树市| 张掖市| 长岛县| 闽清县| 周至县| 南华县| 东丰县| 辰溪县| 铜川市| 博乐市| 隆化县| 乐清市| 金沙县| 泊头市| 玉门市| 九龙城区|