|
點擊上方“C語言與CPP編程”,選擇“關(guān)注/置頂/星標(biāo)公眾號”
干貨福利,第一時間送達(dá)!
最近有小伙伴說沒有收到當(dāng)天的文章推送,這是因為微信改了推送機制,確實會一部分有小伙伴刷不到當(dāng)天的文章,一些比較實用的知識和信息,錯過了就是錯過了。所以建議大家加個星標(biāo)??,就能第一時間收到推送了。
ihfihjrat2s64063237520.png (399.52 KB, 下載次數(shù): 2)
下載附件
保存到相冊
ihfihjrat2s64063237520.png
2024-10-9 01:44 上傳
近年來向開發(fā)者建議放棄 C++、使用內(nèi)存安全語言的聲音越來越多。面對這個情況,本文作者 Sean Baxter 提出了解決問題的關(guān)鍵:并非完全轉(zhuǎn)向 Rust,而是要創(chuàng)建一個包含嚴(yán)格安全子集的 C++ 超集——Circle C++ 由此誕生。
截至目前,Circle 編譯器在 GitHub 已擁有 2.3k+ Star,在 HN 上也有許多開發(fā)者對此表示支持。
原文鏈接:https://www.circle-lang.org/site/intro/
在過去兩年中,美國政府愈發(fā)迫切地警告開發(fā)者不要采用內(nèi)存不安全的編程語言。據(jù)了解,在美國許多關(guān)鍵基礎(chǔ)設(shè)施都依賴于用 C 和 C++ 編寫的軟件,但其內(nèi)存并不安全,政府認(rèn)為這將導(dǎo)致系統(tǒng)很容易被對手利用:
2022 年 11 月 10 日,NSA 發(fā)布《關(guān)于如何防范軟件內(nèi)存安全問題指南》;
2023 年 9 月 20 日,發(fā)布《軟件產(chǎn)品內(nèi)存安全的迫切需》要;
2023 年 12 月 6 日,CISA 發(fā)布《軟件制造商聯(lián)合指南》:內(nèi)存安全路線圖案例;
2024 年 2 月 26 日,發(fā)布《未來軟件應(yīng)具有內(nèi)存安全性》;
2024 年 5 月 7 日,發(fā)布《國家網(wǎng)絡(luò)安全戰(zhàn)略實施計劃》
以上這些政府文件得到了許多行業(yè)研究的支持。例如微軟的漏洞遙測顯示,70% 的漏洞可通過內(nèi)存安全的編程語言來阻止;谷歌研究也發(fā)現(xiàn),68% 的 0day 漏洞與內(nèi)存損壞漏洞有關(guān)。
于是乎,不少安全專業(yè)人士大聲疾呼,要求項目摒棄 C++,開始使用內(nèi)存安全語言——但這絕不是喊喊口號而已。
要知道,由 C++ 所支持的產(chǎn)品創(chuàng)造了數(shù)萬億美元的價值,如今有大量的 C++ 程序員和 C++ 代碼。鑒于 C 和 C++ 代碼的廣泛傳播,業(yè)界究竟能做些什么來提高軟件質(zhì)量和減少漏洞?在現(xiàn)有項目中引入新的內(nèi)存安全代碼并加固現(xiàn)有軟件的方案又有哪些?
有一種系統(tǒng)級/非垃圾回收語言能提供嚴(yán)格的內(nèi)存安全性,那就是 Rust。但是,C++ 和 Rust 卻大相徑庭,互操作能力也有限,因此從 C++ 向 Rust 的增量遷移是一個緩慢而艱苦的過程。
Rust 缺乏函數(shù)重載、模板和繼承,C++ 則缺少 traits、重定位和生命周期參數(shù)。這些差異造成了兩種語言接口時的“阻抗失配”。大多數(shù)跨語言綁定的代碼生成器都沒有嘗試用一種語言來表示另一種語言的特征。它們通常會確定一些特殊的詞匯類型,具有一流的特性但也限制了其他語言的功能。
對于職業(yè) C++ 開發(fā)者來說,Rust 是一種陌生的語言,加上缺乏互操作工具,想要用 Rust 來重寫關(guān)鍵部分以加固 C++ 應(yīng)用是非常困難的。所以說,為什么在語言內(nèi)沒有一個能解決內(nèi)存安全問題的方法?為什么沒有一個安全的 C++(Safe C++)呢?
一、為安全而擴展 C++
我的目標(biāo)是創(chuàng)建一個包含嚴(yán)格安全子集的 C++ 超集。無論是啟動一個新項目,還是在現(xiàn)有項目中編寫安全代碼,都可以在 C++ 中實現(xiàn)——這樣編寫的 C++ 代碼,將與用 Rust 編寫的安全代碼一樣,具有強大的安全保證。事實上,生命周期安全是通過借用檢查(borrow checking)靜態(tài)執(zhí)行的,這正是由 Rust 首次引入的簽名安全技術(shù)。
選擇 Rust 的理由是:這是一種為安全而設(shè)計的全新語言。
選擇 Safe C++ 的理由是:它提供了與 Rust 同樣嚴(yán)格的安全保證,但由于它是對 C++ 的擴展,因此可以無縫地與現(xiàn)有代碼互操作。
我們的目標(biāo)是編寫穩(wěn)健且可靠的軟件。雖然 Rust 已被證明是實現(xiàn)這一目標(biāo)的有效工具,但 Safe C++ 也能成為另一種可行的選擇——不可行的,是繼續(xù)添加不安全、漏洞百出的代碼。
你可能要問了,Safe C++ 具體有哪些特點?
(1)一種包含安全子集的 C++ 超集。在安全子集中,禁止出現(xiàn)未定義的行為。
(2)語言的安全部分和不安全部分界限分明,用戶必須明確需離開安全部分才能進行不安全操作。
(3)安全子集必須保持實用性。如果我們?nèi)コ艘恍╆P(guān)鍵的不安全技術(shù),比如聯(lián)合或指針,就必須提供一個安全的替代方案,比如選擇類型或借用。如果一種語言過于缺乏表達(dá)能力,即便它非常安全也無法完成工作,那它也無用的。
(4)新系統(tǒng)不能破壞現(xiàn)有代碼。如果用 Safe C++ 編譯器編譯現(xiàn)有的 C++ 代碼,那這些代碼就必須能夠正常編譯,用戶也可以選擇是否使用新的安全機制。一定要記住,Safe C++ 是對 C++ 的擴展,而不是一種新語言。
#feature on safety#include "std2.h"
int main() safe { std2::vectorint> vec { 11, 15, 20 };
for(int x : vec) { // Ill-formed. mutate of vec invalidates iterator in ranged-for. if(x % 2) vec^.push_back(x);
unsafe printf("%d
", x); }}$ circle iter3.cxx safety: iter3.cxx:10:10 vec^.push_back(x); ^mutable borrow of vec between its shared borrow and its useloan created at iter3.cxx:7:15 for(int x : vec) { ^考慮上面這個用 Safe C++ 寫的示例,它可以捕捉到迭代器失效,而通常迭代器失效會導(dǎo)致 use-after-free 錯誤。接下來讓我們逐行分析:
第 1 行:#feature on safety - 在當(dāng)前文件中啟用與安全相關(guān)的新關(guān)鍵字。而翻譯單元中的其他文件不受影響,這就是 Safe C++ 避免破壞現(xiàn)有代碼的方式——-所有內(nèi)容都是選擇性的,包括新的關(guān)鍵字和語法。這個安全特性改變了函數(shù)定義的對象模型,使其支持對象重定位、部分初始化和延遲初始化。它將函數(shù)定義轉(zhuǎn)換為中級中間表示(MIR),并在此基礎(chǔ)上進行借用檢查,以標(biāo)記檢查引用中可能潛在的 use-after-free 漏洞。
第 2 行:#include "std2.h" - 引入新的安全容器和算法。加強安全性就是要減少你對不安全 API 的依賴,當(dāng)前的標(biāo)準(zhǔn)庫中充滿了各種不安全的 API,而命名空間 std2 中的新標(biāo)準(zhǔn)庫會提供相同的基本功能,但其容器將具備生命周期感知和類型安全的特性。
第 4 行:int main() safe - 新的 safe 說明符是函數(shù)類型的一部分,類似于 noexcept 說明符。對于調(diào)用者來說,這個函數(shù)被標(biāo)記為安全,因此可以在安全的上下文中調(diào)用。main 的定義開始于一個安全的上下文,因此不允許執(zhí)行不安全的操作,例如取消引用指針或調(diào)用不安全的函數(shù)等。Rust 的函數(shù)默認(rèn)是安全的,而 C++ 的函數(shù)默認(rèn)是不安全的,但這只是語法上的區(qū)別。一旦在 C++ 中使用 safe 說明符進入安全上下文,就會得到與 Rust 同樣嚴(yán)格的安全保證。
第 5 行:std2::vector vec { 11, 15, 20 }; - 一個內(nèi)存安全向量的列表初始化。該向量能夠感知生命周期參數(shù),因此借用檢查將擴展到有生命周期的元素類型。該向量的構(gòu)造函數(shù)沒有使用 std::initializer_list,因為這種類型有兩個問題:首先,用戶會得到指向參數(shù)數(shù)據(jù)的指針,而從指針讀取數(shù)據(jù)是不安全的;其次,std::initializer_list 不擁有其數(shù)據(jù),無法進行重定位;谶@些原因,Safe C++ 引入了 std2::initializer_list,它可以在安全上下文中使用,并支持我們的所有權(quán)對象模型。
第 7 行:for(int x : vec) - 對向量進行基于范圍的 for 循環(huán)。標(biāo)準(zhǔn)機制返回一對迭代器,它們實際上是用類封裝的指針。C++ 的迭代器并不安全,總以 begin 和 end 成對出現(xiàn),但不共享共同的生命周期參數(shù),因此對它們進行借用檢查不太現(xiàn)實。而 Safe C++ 版本使用切片迭代器,類似于 Rust 的迭代器。這些安全類型使用生命周期參數(shù),因此可以很好地防止迭代器失效。
第 10 行:vec^.push_back(x); - 向向量中添加一個值。這里的 ^ 是什么意思?這是一個后綴對象操作符,表示對成員函數(shù)調(diào)用的對象參數(shù)進行可變借用。當(dāng)啟用了 #feature on safety 時,所有的修改操作都是顯式的,這樣在選擇對象的共享借用還是可變借用時更精確。Rust 不支持函數(shù)重載,因此會隱式借用(可變或共享)成員函數(shù)的對象;而 C++ 支持函數(shù)重載,所以需要顯式指定以獲取我們想要的重載。
第 12 行:unsafe printf("%d
", x); - 調(diào)用 printf。這是一個非常不安全的函數(shù),但由于我們在安全的上下文中,所以必須用 unsafe 關(guān)鍵字來轉(zhuǎn)義。Safe C++ 不會鎖定 C++ 語言的任何部分,你可以用 unsafe 關(guān)鍵字,但前提是要明確聲明。用了 unsafe 意味著你承諾遵守函數(shù)的前置條件,而不是依賴編譯器來確保這些前置條件。
如果 main 在語法上通過了檢查,其 AST 會被下放到 MIR,并在那里進行借用檢查。為 ranged-for 循環(huán)提供動力的隱藏迭代器,在循環(huán)執(zhí)行期間保持初始化狀態(tài)。push_back 通過修改迭代器的約束位置(即向量),使迭代器失效。當(dāng)下次從迭代器中加載值 x 時,借用檢查器會報錯:在共享借用和使用之間,vec 存在可變借用。借用檢查器程序可以防止 Circle 編譯出可能存在未定義行為的程序——所有這些都是在編譯時完成的,不會影響程序的大小或速度。
上面這個示例雖然只有幾行,但我引入了許多新的機制和類型。近年來安全專家不斷提醒我們說 C++ 非常不安全,這的確是事實。因此我們才需要付出系統(tǒng)性的努力,提供一個帶有安全子集的語言超集,同時確保其具有足夠的靈活性和表現(xiàn)力。
二、內(nèi)存安全的價值主張
內(nèi)存安全語言的前提,是一個對人類行為的基本觀察:人們傾向于先嘗試一下,如果不行再尋求幫助。放在編程中,就是開發(fā)者會先嘗試用一個庫,只有在不能用時才會閱讀文檔。事實證明,這種做法非常危險,因為能工作的代碼并不一定真正安全。
許多 C++ 函數(shù)都有一些前置條件,只有在仔細(xì)閱讀文檔后才能了解。前置條件可能千奇百怪,開發(fā)者也不能自動知道安全的使用方式是怎樣的。即使表面看起來無害,但違反這些前置條件會導(dǎo)致未定義行為,從而使你的軟件面臨攻擊風(fēng)險。
我認(rèn)為,軟件的安全和保障不應(yīng)依賴于程序員是否嚴(yán)格遵守文檔。
基于此,我想提出一個價值主張:編譯器和庫供應(yīng)商也需要付出額外努力,提供一個穩(wěn)健的環(huán)境,這樣用戶就不必閱讀文檔了。無論他們?nèi)绾问褂谜Z言和庫,都不會引發(fā)未定義行為,也就不會使軟件面臨安全相關(guān)的漏洞。當(dāng)然,沒有一個系統(tǒng)能防止所有誤用,匆忙編寫的代碼可能會有很多邏輯錯誤,但這些邏輯錯誤不會導(dǎo)致內(nèi)存安全漏洞。
上周,我就犯了一個低級錯誤:問題出在 std::isprint 函數(shù)的使用上。這個函數(shù)的參數(shù)是 int 類型,而我當(dāng)時傳入的是 UNICODE 代碼點,沒有考慮到前置條件——參數(shù)必須在 -1 到 255 之間。
我承認(rèn)這是我的問題,但不得不說庫的設(shè)計也違背了人性:不要期望每個程序員在使用函數(shù)之前都會仔細(xì)閱讀文檔!如果內(nèi)存安全語言能提供一個安全穩(wěn)健的環(huán)境,就能防止這種情況的發(fā)生了。
有些內(nèi)存安全問題,比如上述問題,很容易修復(fù)。但在像 ISO C++ 這樣不安全的語言中,有些問題是無法修復(fù)的。僅靠閱讀文檔、遵循 C++ 核心指南或編寫單元測試是不夠的。為了解決生命周期和線程安全問題,需要引入新的語言技術(shù),而這些是全局性問題,需要系統(tǒng)性的解決方案。
三、這是一條未走過的路
許多庫都在嘗試緩解未定義行為的問題,例如檢測器(sanitizers)就是一種特殊的構(gòu)建目標(biāo),它們能在運行時標(biāo)記出未定義行為,這類項目作為防御 C++ 代碼漏洞的第一道防線非常有效。
但是,有哪些努力是直接將內(nèi)存安全特性引入 C++ 語言的呢?除了 Circle C++,目前沒有任何正在進行的項目試圖擴展 C++ 以提供工業(yè)和政府安全研究人員所需的嚴(yán)格內(nèi)存安全保證。沒有人嘗試在 MSVC、Clang 和 GCC 等主流編譯器中構(gòu)建安全上下文,還有那些依賴 C++ 發(fā)展的公司,如微軟、谷歌、NVIDIA、英特爾、Adobe 和彭博社,也都沒有采取相關(guān)措施。甚至 C++ ISO 委員會對這個問題也沒有任何見解和應(yīng)對策略。
為什么編譯器供應(yīng)商和標(biāo)準(zhǔn)化工作者不肯認(rèn)真對待 C++ 中日益嚴(yán)重的安全漏洞問題?我認(rèn)為,這是因為這個問題看起來太難,任何單一的努力都難以取得實質(zhì)性進展:
(1)解決方案要適用的范圍太廣。內(nèi)存安全漏洞種類繁多,涉及生命周期安全、邊界安全、線程安全和各種類型安全等多個方面。每種漏洞都需要單獨處理,這導(dǎo)致整體擴展的工作量非常大。因此有人認(rèn)為,這些變更綜合起來,對于委員會或供應(yīng)商來說工作量實在過于龐大。
(2)需要對工具進行重大升級。不僅編譯器前端需要全面改造,還需要一個新的中端來支持借用檢查和對象重定位。同時,還必須編寫一個新的標(biāo)準(zhǔn)庫,逐步取代舊的庫,減少不安全操作的風(fēng)險——前端、后端加上標(biāo)準(zhǔn)庫的改造,遠(yuǎn)遠(yuǎn)超出了編譯器開發(fā)者的常規(guī)工作范圍。
(3)新技術(shù)難度很大。Rust 安全模型中最獨特的部分是 NLL 借用檢查器,這是一個非常復(fù)雜的功能,光讀一本 Rust 入門書籍或摸索著用這門語言,都無法理解它的工作原理。這個功能的復(fù)雜性嚇退了很多人,他們根本不敢考慮將這項技術(shù)集成到 C++ 編譯器中。雖然這是一個美好的想法,但如果你是一名前端工程師,還沒有廣泛接觸過控制流圖,那就有點像在逼你掌握外星技術(shù)了。
(4)主流編譯器對實驗來說過于繁瑣。C++ 經(jīng)過了 50 多年的演變,從 K&R C 到 C++23,語言極其復(fù)雜,編譯器的編寫和維護也同樣很困難,在 MSVC、Clang 或 GCC 上進行這種級別的實驗非常難。而 Circle 編譯器只有大約 31 萬行代碼,相比之下非常緊湊。每一行代碼都是我寫的,我對每個部分的功能都非常了解,相比在主流工具鏈上工作的人,在這個方面我具有巨大的靈活性優(yōu)勢。
(5)C++ 用戶的傲慢態(tài)度。C 和 C++ 的從業(yè)者常有一種“要做好”的心態(tài),他們認(rèn)為如果你把事情搞砸了,那就是你自己的錯,解決辦法就是提高自己。然而,軟件設(shè)計是一個需要協(xié)作的過程,即使是像我這樣的獨立開發(fā)者也不例外。你總是依賴于他人的代碼,不可能理解它是如何工作的。在大型項目中,期望每個代碼都完美無誤是不現(xiàn)實的。此時,若使用內(nèi)存安全語言會讓錯誤的代價大大降低:你的程序不會出現(xiàn)未定義行為,無法通過編譯器檢查健全性的結(jié)構(gòu)會被標(biāo)記為可能不安全,從而給程序員重新思考設(shè)計的機會,或許用一些內(nèi)存安全的 API 重新實現(xiàn)這些操作。
在《國家網(wǎng)絡(luò)安全實施計劃》中曾提到:
為了開始制定安全軟件開發(fā)的監(jiān)管標(biāo)準(zhǔn),政府將推動制定一個適應(yīng)性安全港框架,保護那些安全開發(fā)和維護其軟件產(chǎn)品和服務(wù)的公司免受責(zé)難……政府將與國會和私營部門合作,制定立法,確定軟件產(chǎn)品和服務(wù)的責(zé)任。C++ 的機構(gòu)用戶應(yīng)該感到擔(dān)憂,安全社區(qū)正在呼吁淘汰這種語言,政府也正在討論對發(fā)布漏洞代碼的公司追究責(zé)任,并為制定安全策略的公司提供免責(zé)保護。此外,禁止在某些行業(yè)中使用 C++ 的立法看起來也有可能成為現(xiàn)實,但 ISO 委員會并沒有重視這個問題的嚴(yán)重性,也沒有應(yīng)對策略或領(lǐng)域?qū)<摇?br />
我認(rèn)為,光是口頭反對國家安全局的說法并不能解決問題,我們需要做點實事。我希望能與 C++ 相關(guān)公司合作,努力解決根本問題。要想讓 C++ 成為開發(fā)者在未來幾十年中既能使用又愿意使用的語言,我們需要付出大量努力。
四、開發(fā)者熱議:Circle C++ 是有價值的,但無人投資
Sean Baxter 對 Circle C++ 的設(shè)想在開發(fā)者圈內(nèi)引起熱議,其中有不少人對此表示支持:
“我認(rèn)為 Sean 的作品是有價格的,但目前還沒有人愿意投資!
“Circle 是唯一一個擁有類似 Typescript 進化路徑的 C++ 后繼者,而且還能提供高質(zhì)量的編譯器。不幸的是,WG21 似乎對 Circle 早期提出的任何想法都有意見,他們應(yīng)該不愿意采納 Sean 的工作。這就很可惜,因為 Sean 單槍匹馬就比整個 C++ 編譯器領(lǐng)域的人交出了更多的成果。”
但同時,也有人對 Circle 擁有 2.3k+ Star、卻 7 個月沒更新的現(xiàn)狀提出質(zhì)疑:“我對開發(fā)者對 Circle 的熱愛感到非常困惑——有一個 GitHub 已經(jīng) 7 個月沒有更新了,而且還沒有許可證,它真的有用戶嗎?看起來這只是一個雛形項目,其理念并未被 cpp 采用,編譯器資源庫也沒更新!
對此,有人回應(yīng)稱 7 個月沒更新是因為 Circle 并非開源項目,并補充道企業(yè)也不會在意它是否開源:“如果 Circle 真的能實現(xiàn)其既定目標(biāo),那么 C++ 內(nèi)存安全超集的價值主張就是巨大的。很多使用 C++ 編寫關(guān)鍵軟件的公司不會在意 Circle 是否開源,只要它能滿足他們的所有要求(認(rèn)證、審核等),他們就能給予它強大的企業(yè)支持!
本文經(jīng)授權(quán)轉(zhuǎn)自公眾號CSDN(ID:CSDNnews)
作者 | Sean Baxter翻譯 | 鄭麗媛——EOF——你好,我是飛宇。日常分享C/C++、計算機學(xué)習(xí)經(jīng)驗、工作體會,歡迎點擊此處查看我以前的學(xué)習(xí)筆記&經(jīng)驗&分享的資源。
我組建了一些社群一起交流,群里有大牛也有小白,如果你有意可以一起進群交流。
44rn1t2ztzi64063237620.png (195.91 KB, 下載次數(shù): 1)
下載附件
保存到相冊
44rn1t2ztzi64063237620.png
2024-10-9 01:44 上傳
歡迎你添加我的微信,我拉你進技術(shù)交流群。此外,我也會經(jīng)常在微信上分享一些計算機學(xué)習(xí)經(jīng)驗以及工作體驗,還有一些內(nèi)推機會。
pg1t0koj2da64063237720.png (281.08 KB, 下載次數(shù): 1)
下載附件
保存到相冊
pg1t0koj2da64063237720.png
2024-10-9 01:44 上傳
加個微信,打開另一扇窗
經(jīng)常遇到有讀者后臺私信想要一些編程學(xué)習(xí)資源,這里分享 1T 的編程電子書、C/C++開發(fā)手冊、Github上182K+的架構(gòu)路線圖、LeetCode算法刷題筆記等精品學(xué)習(xí)資料,點擊下方公眾號會回復(fù)"編程"即可免費領(lǐng)取~
感謝你的分享,點贊,在看三連
4mjakpe3l3064063237820.gif (88.16 KB, 下載次數(shù): 2)
下載附件
保存到相冊
4mjakpe3l3064063237820.gif
2024-10-9 01:44 上傳
|
|