1. LWN:strlcpy( ) 的未來!

        共 3602字,需瀏覽 8分鐘

         ·

        2022-09-17 16:11

        關(guān)注了就能看到更多這么棒的文章哦~

        Ushering out strlcpy()

        By Jonathan Corbet
        August 25, 2022
        DeepL assisted translation
        https://lwn.net/Articles/905777/

        在內(nèi)核中有那么多必須解決的復(fù)雜的問題,因此人們可能認(rèn)為復(fù)制一個(gè)字符串的問題不會(huì)引起什么注意。哪怕有 C 語言 string 帶來的那些危險(xiǎn),簡(jiǎn)單地移動(dòng)幾個(gè)字節(jié)也不應(yīng)該是那么困難。但是,多年來 string-copy 函數(shù)一直是一個(gè)經(jīng)常爭(zhēng)論的話題,只是有時(shí)反映在這一族操作中的某些變種上?,F(xiàn)在看來,源自 BSD 的 strlcpy()函數(shù)可能最終要退出內(nèi)核了。

        起初,在 C 語言中復(fù)制字符串是很簡(jiǎn)單的。編者的《C 語言程序設(shè)計(jì)》第一版的第 101 頁提供了一個(gè) strcpy()的實(shí)現(xiàn):

        strcpy(s, t)
        char *s, *t;
        {
        while (*s++ = *t++)
        ;
        }

        這個(gè)函數(shù)有幾個(gè)缺點(diǎn),其中最明顯的缺點(diǎn)是,如果 s 字符串太長(zhǎng)了,它就會(huì)寫到目標(biāo) buffer t 之后的位置。在 C 語言中進(jìn)行開發(fā)的人們最終認(rèn)為這可能是一個(gè)問題,所以開發(fā)了其他的一些字符串復(fù)制函數(shù),首先是 strncpy():

        char *strncpy(char *dest, char *src, size_t n);

        這個(gè)函數(shù)最多可以從 src 復(fù)制 n 個(gè)字節(jié)到 dest,所以,只要 n 不超過 dest 的長(zhǎng)度,那么這個(gè)數(shù)組就不會(huì)被越界寫入。如果 src 短于 n,那么就會(huì)用 NUL 來完整填充 dest,所以它最終總是寫滿數(shù)組。如果 src 比 n 長(zhǎng),那么 dest 就不可能是以 NUL 結(jié)束的——如果調(diào)用者不仔細(xì)檢查返回值的話就會(huì)出問題。返回值是寫入 dest 的第一個(gè) NUL 字符的位置。如果 src 太長(zhǎng),那么,strncpy()返回 &dest[n],這實(shí)際上是超出實(shí)際數(shù)組 dest 的地址,不管是否進(jìn)行了截?cái)啵╰runcation)。因此,要想檢查出是否有截?cái)啵€是有些棘手,而且人們經(jīng)常不做這個(gè)檢查。[感謝 Rasmus Villemoes 指出了我們先前對(duì) strncpy()返回值的描述中的錯(cuò)誤。]

        strlcpy() and strscpy()

        BSD 針對(duì) strncpy()的問題采用的解決方案是引入了一個(gè)新的函數(shù),叫做 strlcpy():

        size_t strlcpy(char *dest, const char *src, size_t n);

        這個(gè)函數(shù)也將從 src 復(fù)制最多 n 個(gè)字節(jié)到 dest;與 strncpy()不同的是,它將始終確保 dest 是 NUL 結(jié)尾的。返回值永遠(yuǎn)是 src 的長(zhǎng)度,不管它在復(fù)制過程中是否被截?cái)噙^;開發(fā)者必須將返回的長(zhǎng)度與 n 進(jìn)行比較,來確定是否發(fā)生了截?cái)唷?br>

        某種意義上來說,strlcpy()在內(nèi)核中首次使用是在 2.4 穩(wěn)定版。media 子系統(tǒng)有如下幾個(gè)實(shí)現(xiàn):

        #define strlcpy(dest,src,len) strncpy(dest,src,(len)-1)

        可見,當(dāng)時(shí)對(duì)返回值并沒有進(jìn)行什么檢查。這個(gè)宏很快就消失了,但真正的 strlcpy() 實(shí)現(xiàn)在 2003 年 5 月的 2.5.70 版本中出現(xiàn)了。該版本也將內(nèi)核中的許多調(diào)用位置的代碼都轉(zhuǎn)換為使用這個(gè)新函數(shù)了。在相當(dāng)長(zhǎng)的一段時(shí)間內(nèi),似乎一切都能正常工作。

        但在 2014 年,人們開始聽到對(duì) strlcpy()的批評(píng),這導(dǎo)致了一個(gè)長(zhǎng)時(shí)間討論,關(guān)于是否在 GNU C 庫(kù)中增加相關(guān)實(shí)現(xiàn);直到今天,glibc 還是沒有 strlcpy()。內(nèi)核開發(fā)者也開始對(duì)這個(gè) API 感到不太滿意了。2015 年,Chris Metcalf 在內(nèi)核中加入了另一個(gè)字符串拷貝函數(shù):

        ssize_t strscpy(char *dest, const char *src, size_t count);

        這個(gè)函數(shù)跟其他類似函數(shù)一樣,都是將 src 復(fù)制到 dest,確保不會(huì)超過后者的邊界。和 strlcpy()一樣,它也確保結(jié)果是 NUL 結(jié)尾的。區(qū)別在于返回值上;如果字符串符合要求的話,返回的就是復(fù)制的字符數(shù)(不包括尾部的 NUL 字節(jié)),否則是 -E2BIG。

        Reasons to like strscpy()

        為什么 strscpy()更好?人們所宣傳的一個(gè)優(yōu)勢(shì)就是返回值,這使得檢查 src 字符串是否被截?cái)嘧兊煤苋菀?。不過還有一些其他的優(yōu)點(diǎn);要了解這些優(yōu)點(diǎn),可以先看一下內(nèi)核對(duì) strlcpy()的實(shí)現(xiàn):

        size_t strlcpy(char *dest, const char *src, size_t size)
        {
        size_t ret = strlen(src);

        if (size) {
        size_t len = (ret >= size) ? size - 1 : ret;
        memcpy(dest, src, len);
        dest[len] = '\0';
        }
        return ret;
        }

        這里有一個(gè)明顯的缺點(diǎn),這個(gè)函數(shù)將讀取整個(gè) src 字符串,而不管這些數(shù)據(jù)是否會(huì)被用來復(fù)制。考慮到 strlcpy() 的定義語義,這里的低效做法無法根本解決;沒有其他方法可以返回 src 字符串的長(zhǎng)度。不過,這不僅僅是一個(gè)效率問題;正如 Linus Torvalds 最近指出的那樣,如果 src 字符串不可信,就會(huì)發(fā)生不好的事情,而這個(gè)函數(shù)本來就希望用在這種情況下。如果 src 不是以 NUL 為結(jié)尾的,那么 strlcpy() 就會(huì)繼續(xù)愉快地往下執(zhí)行,直到它找到一個(gè) NUL 字節(jié),這可能會(huì)遠(yuǎn)遠(yuǎn)超出了 src 數(shù)組的范圍,如果它沒有先出現(xiàn) crash 的話。

        最后,strlcpy() 會(huì)導(dǎo)致出現(xiàn)一個(gè) race condition。src 的長(zhǎng)度先被計(jì)算出來,然后用于進(jìn)行復(fù)制操作,最終返回給調(diào)用者。但是如果 src 在這個(gè)過程中發(fā)生了改變,就會(huì)出現(xiàn)一些奇怪的事情;最好的情況是,返回值與目的地字符串中的實(shí)際內(nèi)容不完全相同。這個(gè)問題是實(shí)現(xiàn)上細(xì)節(jié)問題,而不是定義方面的問題,因此可以被 fix,但似乎沒有人認(rèn)為值得去 fix。

        strscpy() 的實(shí)現(xiàn)避免了所有這些問題,也更有效率。當(dāng)然,它也因此而更加復(fù)雜。

        The end of strlcpy() in the kernel?

        當(dāng) strlcpy() 第一次被引入時(shí),其目的是取代內(nèi)核中所有的 strncpy() 調(diào)用,完全替代并刪除后者。但在 6.0-rc2 內(nèi)核中,仍然有近 900 個(gè) strncpy()的調(diào)用位置;這個(gè)數(shù)字在 6.0 合并窗口中還增加了兩個(gè)。在引入 strscpy()時(shí),Torvalds 明確表示不希望看到大規(guī)模地把 strlcpy() 調(diào)用批量切換過去的做法。在 6.0-rc2 中,只有 1400 多個(gè) strlcpy()調(diào)用和近 1800 個(gè) strscpy()調(diào)用。

        將近七年之后,他的態(tài)度似乎發(fā)生了一些變化;Torvalds 現(xiàn)在說,"strlcpy()確實(shí)需要被淘汰了"。一些子系統(tǒng)已經(jīng)進(jìn)行了轉(zhuǎn)換,自 5.19 以來,strlcpy()的調(diào)用位置數(shù)量已經(jīng)減少了 85 個(gè)。是否有可能完全刪除 strlcpy(),目前還不清楚。盡管 strncpy()的危害是眾所周知的,而且近 20 年前就已經(jīng)決定將其刪除,但它仍然存在著。一旦有什么功能進(jìn)入了內(nèi)核,再把它移除,可能就是一個(gè)很困難的過程了。

        不過,這次這個(gè)可能還有希望。正如 Torvalds 在回應(yīng) Wolfram Sang 的一組轉(zhuǎn)換時(shí)觀察到的那樣,大多數(shù)調(diào)用 strcpy() 的地方從未使用過返回值;這些地方都可以被轉(zhuǎn)換為 strscpy()而不會(huì)改變其行為。他建議,所需要的只是有人創(chuàng)建一個(gè) Coccinelle 腳本來完成這項(xiàng)工作。Sang 接受了這個(gè)挑戰(zhàn),并創(chuàng)建了一個(gè)完成轉(zhuǎn)換的 branch。顯然,這個(gè)工作不會(huì)被考慮加到 6.0 中,但可能會(huì)出現(xiàn)在 6.1 的 pull request 中。

        這將在內(nèi)核中留下相對(duì)較少的 strlcpy() 使用代碼。這些代碼可以被一個(gè)一個(gè)地清理掉,而且有可能完全擺脫 strlcpy()。這將結(jié)束 20 年來在內(nèi)核中進(jìn)行有邊界的字符串拷貝(bounded string copy)的最佳方式的時(shí)不時(shí)地討論,盡管還有一些剩余的 strncpy()調(diào)用。至少在今后哪位聰明的開發(fā)者想出一個(gè)更好的函數(shù)并重新再來一次之前。

        全文完
        LWN 文章遵循 CC BY-SA 4.0 許可協(xié)議。

        歡迎分享、轉(zhuǎn)載及基于現(xiàn)有協(xié)議再創(chuàng)作~

        長(zhǎng)按下面二維碼關(guān)注,關(guān)注 LWN 深度文章以及開源社區(qū)的各種新近言論~



        瀏覽 93
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
          
          

            1. 91午夜福利视频 | 囯产精品久久久久久 | 五月色小说 | 日本精品秘 入口免费视频 | 欧美交换配乱婬粗大嫩模 |