国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

完全可復制、經(jīng)過驗證的 Go 工具鏈

這篇具有很好參考價值的文章主要介紹了完全可復制、經(jīng)過驗證的 Go 工具鏈。希望對大家有所幫助。如果存在錯誤或未考慮完全的地方,請大家不吝賜教,您也可以點擊"舉報違法"按鈕提交疑問。

原文在這里。

由 Russ Cox 發(fā)布于 2023年8月28日

開源軟件的一個關(guān)鍵優(yōu)勢是任何人都可以閱讀源代碼并檢查其功能。然而,大多數(shù)軟件,甚至是開源軟件,都以編譯后的二進制形式下載,這種形式更難以檢查。如果攻擊者想對開源項目進行供應(yīng)鏈攻擊,最不可見的方式是替換正在提供的二進制文件,同時保持源代碼不變。

解決這種類型的攻擊的最佳方法是使開源軟件的構(gòu)建具有可重現(xiàn)性,這意味著以相同的源代碼開始的每個構(gòu)建都會產(chǎn)生相同的輸出。這樣,任何人都可以通過從真實源代碼構(gòu)建并檢查重建的二進制文件是否與已發(fā)布的二進制文件完全相同來驗證發(fā)布的二進制文件是否沒有隱藏的更改。這種方法證明了二進制文件沒有后門或源代碼中不存在的其他更改,而無需分解或查看其中的內(nèi)容。由于任何人都可以驗證二進制文件,因此獨立的團體可以輕松檢測并報告供應(yīng)鏈攻擊。

隨著供應(yīng)鏈安全的重要性日益增加,可重現(xiàn)構(gòu)建變得越來越重要,因為它們提供了一種驗證開源項目已發(fā)布的二進制文件的簡單方式。

Go 1.21.0 是第一個具有完全可重現(xiàn)構(gòu)建的 Go 工具鏈。以前的工具鏈也可以重現(xiàn),但需要付出大量的努力,而且可能沒有人這樣做:他們只是相信在 go.dev/dl 上發(fā)布的二進制文件是正確的?,F(xiàn)在,“信任但要驗證”變得容易了。

本文解釋了使構(gòu)建具有可重現(xiàn)性所需的內(nèi)容,檢查了我們必須對 Go 進行的許多更改,以使 Go 工具鏈具有可重現(xiàn)性,并通過驗證 Go 1.21.0 的 Ubuntu 包的一個好處來演示可重現(xiàn)性之一。

使構(gòu)建具有可重現(xiàn)性

計算機通常是確定性的,因此您可能認為所有構(gòu)建都將同樣可重現(xiàn)。從某種意義上說,這是正確的。讓我們將某個信息稱為相關(guān)輸入,當構(gòu)建的輸出取決于該輸入時。如果構(gòu)建可以重復使用所有相同的相關(guān)輸入,那么構(gòu)建是可重現(xiàn)的。不幸的是,許多構(gòu)建工具事實上包含了我們通常不會意識到是相關(guān)的輸入,而且可能難以重新創(chuàng)建或提供作為輸入。當輸入事實上是相關(guān)的但我們沒有打算讓它成為相關(guān)輸入時,讓我們稱之為意外輸入。

構(gòu)建系統(tǒng)中最常見的意外輸入是當前時間。如果構(gòu)建將可執(zhí)行文件寫入磁盤,文件系統(tǒng)會將當前時間記錄為可執(zhí)行文件的修改時間。如果構(gòu)建然后使用類似于 “tar” 或 “zip” 之類的工具打包該文件,那么修改時間將寫入存檔中。我們當然不希望構(gòu)建根據(jù)當前時間更改,但實際上它確實發(fā)生了。因此,當前時間事實上成為構(gòu)建的意外輸入。更糟糕的是,大多數(shù)程序都不允許您將當前時間提供為輸入,因此沒有辦法重復此構(gòu)建。為了解決這個問題,我們可以將創(chuàng)建的文件的時間戳設(shè)置為 Unix 時間 0 或從構(gòu)建的某個源文件中讀取的特定時間。這樣,當前時間不再是構(gòu)建的相關(guān)輸入。

構(gòu)建的常見相關(guān)輸入包括:

  • 要構(gòu)建的源代碼的特定版本;
  • 將包括在構(gòu)建中的依賴項的特定版本;
  • 運行構(gòu)建的操作系統(tǒng),這可能會影響生成的二進制文件中的路徑名;
  • 構(gòu)建系統(tǒng)上運行的CPU架構(gòu),這可能會影響編譯器使用的優(yōu)化或某些數(shù)據(jù)結(jié)構(gòu)的布局;
  • 正在使用的編譯器版本以及傳遞給它的編譯器選項,這會影響代碼的編譯方式;
  • 包含源代碼的目錄的名稱,這可能會出現(xiàn)在調(diào)試信息中;
  • 運行構(gòu)建的帳戶的用戶名、組名、uid和gid,這可能會出現(xiàn)在存檔中的文件元數(shù)據(jù)中;
  • 還有許多其他因素。

要使構(gòu)建具有可重現(xiàn)性,每個相關(guān)輸入都必須在構(gòu)建中是可配置的,然后必須將二進制文件發(fā)布在明確列出了每個相關(guān)輸入的配置旁邊。如果你已經(jīng)做到了這一點,那么你有一個可重現(xiàn)的構(gòu)建。恭喜!

但我們還沒有完成。如果只有在首先找到具有正確體系結(jié)構(gòu)的計算機,安裝特定操作系統(tǒng)版本,編譯器版本,將源代碼放在正確目錄中,正確設(shè)置用戶身份等情況下才能重現(xiàn)這些二進制文件,那么在實踐中這可能是太麻煩了。

我們希望構(gòu)建不僅具有可重現(xiàn)性,而且易于重現(xiàn)。為此,我們需要識別相關(guān)輸入,然后不是僅僅將它們記錄下來,而是消除它們。構(gòu)建顯然必須依賴于正在構(gòu)建的源代碼,但其他一切都可以被消除。當構(gòu)建的唯一相關(guān)輸入是其源代碼時,我們可以稱之為完全可重現(xiàn)的

完全可重現(xiàn)的 Go 構(gòu)建

從 Go 1.21 版本開始,Go 工具鏈具有完全可重現(xiàn)的特性:它的唯一相關(guān)輸入是該構(gòu)建的源代碼。我們可以在支持 Go 的任何主機上構(gòu)建特定的工具鏈(例如,針對 Linux/x86-64 的 Go),包括在 Linux/x86-64 主機、Windows/ARM64 主機、FreeBSD/386 主機或其他支持 Go 的主機上構(gòu)建,并且可以使用任何 Go 引導編譯器,包括一直追溯到 Go 1.4 的 C 實現(xiàn)的引導編譯器,還可以改變其他任何細節(jié)。但這些都不會改變構(gòu)建出來的工具鏈。如果我們從相同的工具鏈源代碼開始,我們將得到完全相同的工具鏈二進制文件。

這種完全可重現(xiàn)性是自從 Go 1.10 以來努力的巔峰,盡管大部分工作集中在 Go 1.20 和 Go 1.21 中進行。以下是一些最有趣的相關(guān)輸入,它們被消除了,從而實現(xiàn)了這種完美的可重現(xiàn)性。

在 Go 1.10 中的可重現(xiàn)性

Go 1.10 引入了一個內(nèi)容感知的構(gòu)建緩存,它根據(jù)構(gòu)建輸入的指紋而不是文件修改時間來決定目標是否為最新。因為工具鏈本身是這些構(gòu)建輸入之一,而且 Go 是用 Go 編寫的,所以引導過程只有在單臺機器上的工具鏈構(gòu)建是可重復的情況下才能收斂。整個工具鏈構(gòu)建過程如下:

完全可復制、經(jīng)過驗證的 Go 工具鏈

我們首先使用早期版本的 Go 構(gòu)建當前 Go 工具鏈的源代碼,這個早期版本是引導工具鏈(Go 1.10 使用 Go 1.4,用 C 編寫;Go 1.21 使用 Go 1.17)。這會生成 "toolchain1",然后我們再次使用 "toolchain1" 來構(gòu)建一切,生成 "toolchain2",接著使用 "toolchain2" 再次構(gòu)建一切,生成 "toolchain3"。

"toolchain1" 和 "toolchain2" 是從相同的源代碼構(gòu)建的,但使用了不同的 Go 實現(xiàn)(編譯器和庫),所以它們的二進制文件肯定是不同的。然而,如果這兩個 Go 實現(xiàn)都是非有錯誤的、正確的實現(xiàn),那么 "toolchain1" 和 "toolchain2" 應(yīng)該表現(xiàn)完全相同。特別是,當給出 Go 1.X 源代碼時,"toolchain1" 的輸出("toolchain2")和 "toolchain2" 的輸出("toolchain3")應(yīng)該是相同的,這意味著 "toolchain2" 和 "toolchain3" 應(yīng)該是相同的。

至少,這是理論上的想法。在實際操作中,要使其成為真實情況,需要消除一些無意的輸入:

在構(gòu)建系統(tǒng)中,有一些常見的無意的輸入(unintentional inputs)可能導致構(gòu)建的結(jié)果不可重復,這里介紹了其中兩個主要問題:

隨機性(Randomness):在使用多個 Goroutines 和鎖進行序列化的情況下,例如地圖迭代和并行工作,可能會引入結(jié)果生成的順序上的隨機性。這種隨機性會導致工具鏈每次運行時產(chǎn)生幾種不同的可能輸出之一。為了使構(gòu)建可重復,必須找到這些隨機性,并在用于生成輸出之前對相關(guān)項目的列表進行排序。

引導庫(Bootstrap Libraries):編譯器使用的任何庫,如果它可以從多個不同的正確輸出中選擇,可能會在不同的 Go 版本之間更改其輸出。如果該庫的輸出更改導致編譯器輸出更改,那么 "toolchain1" 和 "toolchain2" 將不會在語義上相同,"toolchain2" 和 "toolchain3" 也不會在比特位上相同。

一個經(jīng)典的例子是 sort 包,它可以以任何順序放置比較相等的元素。寄存器分配器可能會根據(jù)常用變量對其進行排序,鏈接器會根據(jù)大小對數(shù)據(jù)段中的符號進行排序。為了完全消除排序算法的任何影響,使用的比較函數(shù)不能將兩個不同的元素報告為相等。在實踐中,要在工具鏈的每次使用 sort 的地方強制執(zhí)行這種不變性太困難,因此我們安排將 Go 1.X 中的 sort 包復制到呈現(xiàn)給引導編譯器的源代碼樹中。這樣,編譯器在使用引導工具鏈時將使用相同的排序算法,就像在使用自身構(gòu)建時一樣。

另一個我們不得不復制的包是 compress/zlib,因為鏈接器會寫入壓縮的調(diào)試信息,而對壓縮庫的優(yōu)化可能會更改精確的輸出。隨著時間的推移,我們還將其他包添加到了這個列表中。這種方法的額外好處是允許 Go 1.X 編譯器立即使用這些包中添加的新 API,但代價是這些包必須編寫以與較舊版本的 Go 兼容。

在 Go 1.20 中的可重現(xiàn)性

Go 1.20 為易于重現(xiàn)的構(gòu)建和工具鏈管理做了準備,通過從工具鏈構(gòu)建中移除兩個相關(guān)輸入來解決了更多的問題。

主機 C 工具鏈:一些 Go 包,尤其是 net 包,默認在大多數(shù)操作系統(tǒng)上使用 cgo。在某些情況下,比如 macOS 和 Windows,使用 cgo 調(diào)用系統(tǒng) DLL 是解析主機名的唯一可靠方法。然而,當我們使用 cgo 時,會調(diào)用主機的 C 工具鏈(即特定的 C 編譯器和 C 庫),不同的工具鏈具有不同的編譯算法和庫代碼,從而產(chǎn)生不同的輸出。一個使用 cgo 的包的構(gòu)建圖如下所示:

完全可復制、經(jīng)過驗證的 Go 工具鏈

因此,主機的 C 工具鏈是預編譯的 net.a(與工具鏈一起提供的庫文件)的相關(guān)輸入。在 Go 1.20 中,我們決定通過從工具鏈中刪除 net.a 來解決這個問題。換句話說,Go 1.20 停止提供預編譯的包來填充構(gòu)建緩存。現(xiàn)在,當程序第一次使用 net 包時,Go 工具鏈會使用本地系統(tǒng)的 C 工具鏈進行編譯并緩存結(jié)果。除了從工具鏈構(gòu)建中刪除相關(guān)輸入和減小工具鏈下載的大小外,不提供預編譯包還使工具鏈下載更加便攜。如果我們在一個系統(tǒng)上使用一個 C 工具鏈構(gòu)建 net 包,然后在不同的系統(tǒng)上使用不同的 C 工具鏈編譯程序的其他部分,通常不能保證這兩部分可以鏈接在一起。

最初我們提供預編譯的 net 包的一個原因是允許在沒有安裝 C 工具鏈的系統(tǒng)上構(gòu)建使用 net 包的程序。如果沒有預編譯的包,那么在這些系統(tǒng)上會發(fā)生什么呢?答案因操作系統(tǒng)而異,但在所有情況下,我們都安排好了 Go 工具鏈,以便繼續(xù)很好地構(gòu)建純 Go 程序,而無需主機的 C 工具鏈。

  • 在 macOS 上,我們重寫了 package net,使用了 cgo 使用的底層機制,而沒有實際的 C 代碼。這樣可以避免調(diào)用主機的 C 工具鏈,但仍然生成一個引用所需系統(tǒng) DLLs 的二進制文件。這種方法之所以可行,是因為每臺 Mac 都安裝了相同的動態(tài)庫。使非 cgo macOS 版本的 package net 使用系統(tǒng) DLLs 也意味著交叉編譯的 macOS 可執(zhí)行文件現(xiàn)在使用系統(tǒng) DLLs 進行網(wǎng)絡(luò)訪問,解決了一個長期存在的功能請求。
  • 在 Windows 上,package net 已經(jīng)直接使用 DLLs 而沒有 C 代碼,因此不需要進行任何更改。
  • 在 Unix 系統(tǒng)上,我們不能假定網(wǎng)絡(luò)代碼的特定 DLL 接口,但純 Go 版本對于使用典型 IP 和 DNS 設(shè)置的系統(tǒng)來說效果很好。此外,在 Unix 系統(tǒng)上安裝 C 工具鏈要容易得多,而在 macOS 和尤其是 Windows 上則要困難得多。我們更改了 go 命令,根據(jù)系統(tǒng)是否安裝了 C 工具鏈,自動啟用或禁用 cgo。沒有 C 工具鏈的 Unix 系統(tǒng)將退回到 package net 的純 Go 版本,在極少數(shù)情況下,如果這還不夠好,它們可以安裝 C 工具鏈。

在刪除了預編譯包之后,Go 工具鏈中仍然依賴于主機 C 工具鏈的部分是使用 package net 構(gòu)建的二進制文件,特別是 go 命令。有了 macOS 的改進,現(xiàn)在可以使用 cgo 禁用構(gòu)建這些命令,完全消除了主機 C 工具鏈作為輸入的問題,但我們將這最后一步留給了 Go 1.21。

主機動態(tài)鏈接器:當程序在使用動態(tài)鏈接的 C 庫的系統(tǒng)上使用 cgo 時,生成的二進制文件會包含系統(tǒng)的動態(tài)鏈接器路徑,類似于 /lib64/ld-linux-x86-64.so.2。如果路徑錯誤,二進制文件將無法運行。通常,每種操作系統(tǒng)/架構(gòu)組合都有一個正確的路徑。不幸的是,像 Alpine Linux 這樣的基于 musl 的 Linux 和像 Ubuntu 這樣的基于 glibc 的 Linux 使用不同的動態(tài)鏈接器。為了使 Go 在 Alpine Linux 上運行,Go 引導過程如下:

完全可復制、經(jīng)過驗證的 Go 工具鏈

引導程序 cmd/dist 檢查了本地系統(tǒng)的動態(tài)鏈接器,并將該值寫入一個新的源文件,與其余鏈接器源代碼一起編譯,實際上將默認值硬編碼到鏈接器本身。然后,當鏈接器從一組已編譯的包構(gòu)建程序時,它使用該默認值。結(jié)果是,在 Alpine 上構(gòu)建的 Go 工具鏈與在 Ubuntu 上構(gòu)建的工具鏈不同:主機配置是工具鏈構(gòu)建的一個相關(guān)輸入。這是一個可重復性問題,但也是一個可移植性問題:在 Alpine 上構(gòu)建的 Go 工具鏈不會在 Ubuntu 上構(gòu)建可工作的二進制文件,反之亦然。

對于 Go 1.20,我們采取了一步措施來解決可重復性問題,即在運行時更改鏈接器,以便在運行時咨詢主機配置,而不是在工具鏈構(gòu)建時硬編碼默認值:

完全可復制、經(jīng)過驗證的 Go 工具鏈

這解決了在 Alpine Linux 上鏈接器二進制文件的可移植性問題,盡管工具鏈整體上沒有解決,因為 go 命令仍然使用了 package net,因此也使用了 cgo,因此在其自身的二進制文件中有一個動態(tài)鏈接器引用。就像前一節(jié)一樣,編譯 go 命令時禁用 cgo 將解決這個問題,但我們將這個更改留到了 Go 1.21 版本中(我們覺得在 Go 1.20 版本周期內(nèi)沒有足夠的時間來充分測試這個更改)。

Go 1.21 中的復現(xiàn)性

在 Go 1.21 中,完美可復現(xiàn)性的目標在望,我們處理了其余的,主要是一些小的相關(guān)輸入。

Host C toolchain and dynamic linker(主機C工具鏈和動態(tài)鏈接器):在 Go 1.20 中,已經(jīng)采取了一些重要措施來消除主機C工具鏈和動態(tài)鏈接器作為相關(guān)輸入的問題。Go 1.21 則通過禁用cgo來完成了消除這些相關(guān)輸入的工作。這提高了工具鏈的可移植性。Go 1.21 是第一個可以在Alpine Linux系統(tǒng)上無需修改就能運行的標準Go工具鏈版本。

去除這些相關(guān)的輸入使得可以在不損失功能的情況下從不同系統(tǒng)進行交叉編譯 Go 工具鏈成為可能。這反過來提高了 Go 工具鏈的供應(yīng)鏈安全性:現(xiàn)在我們可以使用受信任的 Linux/x86-64 系統(tǒng)為所有目標系統(tǒng)構(gòu)建 Go 工具鏈,而不需要為每個目標系統(tǒng)安排一個單獨的受信任系統(tǒng)。因此,Go 1.21 是首個在 go.dev/dl/ 中發(fā)布適用于所有系統(tǒng)的二進制文件的版本。

Source directory(源代碼目錄):Go程序包含了運行時和調(diào)試元數(shù)據(jù)中的完整路徑,以便在程序崩潰或在調(diào)試器中運行時,堆棧跟蹤包含源文件的完整路徑,而不僅僅是文件名。不幸的是,包含完整路徑使源代碼存儲目錄成為構(gòu)建的相關(guān)輸入。為了解決這個問題,Go 1.21 將發(fā)布工具鏈構(gòu)建更改為使用go install -trimpath來安裝命令,將源目錄替換為代碼的模塊路徑。這樣,如果發(fā)布的編譯器崩潰,堆棧跟蹤將打印類似cmd/compile/main.go的路徑,而不是/home/user/go/src/cmd/compile/main.go。由于完整路徑將引用不同機器上的目錄,這個重寫不會有損失。另外,在非發(fā)布構(gòu)建中,保留完整路徑,以便在開發(fā)人員自身導致編譯器崩潰時,IDE和其他工具可以輕松找到正確的源文件。

Host operating system(主機操作系統(tǒng)):Windows系統(tǒng)上的路徑是用反斜杠分隔的,如 cmd\compile\main.go 。而其他系統(tǒng)使用正斜杠,如 cmd/compile/main.go 。盡管早期版本的Go已經(jīng)規(guī)范化了大多數(shù)這些路徑以使用正斜杠,但某種不一致性又重新出現(xiàn)了,導致Windows上的工具鏈構(gòu)建略有不同。我們找到并修復了這個錯誤。

Host architecture(主機架構(gòu)):Go可以運行在各種ARM系統(tǒng)上,并且可以使用軟件浮點數(shù)庫(SWFP)或使用硬件浮點指令(HWFP)來生成代碼。默認使用其中一種模式的工具鏈將會有所不同。就像我們之前在動態(tài)鏈接器中看到的那樣,Go引導過程會檢查構(gòu)建系統(tǒng),以確保生成的工具鏈在該系統(tǒng)上可以正常工作。出于歷史原因,規(guī)則是“假設(shè)SWFP,除非構(gòu)建運行在帶有浮點硬件的ARM系統(tǒng)上”,跨編譯工具鏈會假定為SWFP。如今,絕大多數(shù)ARM系統(tǒng)都配備了浮點硬件,因此這引入了本地編譯和跨編譯工具鏈之間不必要的差異,而且進一步復雜的是,Windows ARM構(gòu)建始終假定為HWFP,使這個決策依賴于操作系統(tǒng)。我們將規(guī)則更改為“假設(shè)HWFP,除非構(gòu)建運行在不帶浮點硬件的ARM系統(tǒng)上”。這樣,跨編譯和在現(xiàn)代ARM系統(tǒng)上構(gòu)建將產(chǎn)生相同的工具鏈。

Packaging logic(打包邏輯):用于創(chuàng)建我們發(fā)布供下載的工具鏈檔案的所有代碼都存儲在單獨的Git存儲庫中(golang.org/x/build),檔案的確切細節(jié)隨時間而變。如果要重現(xiàn)這些檔案,您需要具有該存儲庫的正確版本。我們通過將代碼移動到Go主源代碼樹中(作為cmd/distpack)來消除了這個相關(guān)輸入。截至Go 1.21,如果您擁有特定版本的Go源代碼,那么您也擁有打包檔案的源代碼。golang.org/x/build存儲庫不再是相關(guān)輸入。

User IDs(用戶ID):我們發(fā)布供下載的tar檔案是從寫入文件系統(tǒng)的分發(fā)構(gòu)建的,并且使用tar.FileInfoHeader將用戶和組ID從文件系統(tǒng)復制到tar文件中,使運行構(gòu)建的用戶成為相關(guān)輸入。我們通過修改打包代碼來清除這些相關(guān)輸入。

Current time(當前時間):與用戶ID一樣,我們發(fā)布供下載的tar和zip檔案也是通過將文件系統(tǒng)修改時間復制到檔案中來構(gòu)建的,使當前時間成為相關(guān)輸入。我們可以清除時間,但我們認為這可能看起來會出人意料,甚至可能會破壞一些工具,因為它使用Unix或MS-DOS的零時間。相反,我們更改了存儲庫中的go/VERSION文件,以添加與該版本關(guān)聯(lián)的時間:

$ cat go1.21.0/VERSION
go1.21.0
time 2023-08-04T20:14:06Z
$

現(xiàn)在,打包工具在將文件寫入存檔時會復制VERSION文件中的時間,而不是復制本地文件的修改時間。

Cryptographic signing keys(加密簽名密鑰):macOS上的Go工具鏈除非我們使用獲得蘋果批準的簽名密鑰對二進制文件進行簽名,否則不會在最終用戶系統(tǒng)上運行。我們使用一個內(nèi)部系統(tǒng)來使用Google的簽名密鑰對它們進行簽名,顯然,我們不能分享該秘密密鑰以允許其他人復制已簽名的二進制文件。相反,我們編寫了一個驗證器,可以檢查兩個二進制文件是否相同,除了它們的簽名。

OS-specific packagers(操作系統(tǒng)特定的打包工具):我們使用Xcode工具的pkgbuild和productbuild來創(chuàng)建可下載的macOS PKG安裝程序,使用WiX來創(chuàng)建可下載的Windows MSI安裝程序。我們不希望驗證器需要完全相同版本的這些工具,所以我們采用了與加密簽名密鑰相同的方法,編寫了一個驗證器,可以查看軟件包內(nèi)部并檢查工具鏈文件是否與預期完全相同。

驗證Go工具鏈

僅一次性使Go工具鏈可重復是不夠的。我們希望確保它們保持可重復性,也希望確保其他人能夠輕松地復制它們。

為了保持自己的誠實,我們現(xiàn)在在受信任的Linux/x86-64系統(tǒng)和Windows/x86-64系統(tǒng)上構(gòu)建所有Go發(fā)行版。除了架構(gòu)之外,這兩個系統(tǒng)幾乎沒有共同之處。這兩個系統(tǒng)必須生成位對位相同的存檔,否則我們不會繼續(xù)發(fā)布。

為了讓其他人驗證我們的誠實,我們編寫并發(fā)布了一個驗證器,golang.org/x/build/cmd/gorebuild。該程序?qū)奈覀兊腉it存儲庫中的源代碼開始重新構(gòu)建當前的Go版本,并檢查它們是否與在 go.dev/dl 上發(fā)布的存檔匹配。大多數(shù)存檔必須位對位匹配。如上所述,有三個例外情況,其中使用更寬松的檢查:

  • macOS tar.gz文件預計會有所不同,但然后驗證器會比較內(nèi)部內(nèi)容。重新構(gòu)建和發(fā)布的副本必須包含相同的文件,并且所有文件必須完全匹配,除了可執(zhí)行二進制文件。在剝離代碼簽名后,可執(zhí)行二進制文件必須完全匹配。
  • macOS PKG安裝程序不會被重新構(gòu)建。相反,驗證器會讀取PKG安裝程序內(nèi)部的文件并檢查它們是否與macOS tar.gz完全匹配,同樣是在剝離代碼簽名后。從長遠來看,PKG創(chuàng)建足夠簡單,可以潛在地添加到cmd/distpack,但驗證器仍然必須解析PKG文件以運行忽略簽名的代碼可執(zhí)行文件比較。
  • Windows MSI安裝程序不會被重新構(gòu)建。相反,驗證器會調(diào)用Linux程序msiextract來提取內(nèi)部文件,并檢查它們是否與重新構(gòu)建的Windows zip文件完全匹配。從長遠來看,可能可以將MSI創(chuàng)建添加到cmd/distpack,然后驗證器可以使用位對位的MSI比較。

我們每晚運行g(shù)orebuild,并在 go.dev/rebuild 上發(fā)布結(jié)果,當然其他任何人也可以運行它。

驗證Ubuntu的Go工具鏈

Go工具鏈的易重現(xiàn)構(gòu)建應(yīng)該意味著在go.dev上發(fā)布的工具鏈中的二進制文件與其他打包系統(tǒng)中包含的二進制文件相匹配,即使這些打包程序是從源代碼構(gòu)建的。即使打包程序使用了不同的配置或其他更改進行編譯,易于重現(xiàn)的構(gòu)建仍然應(yīng)該使復制它們的二進制文件變得容易。為了證明這一點,讓我們復制Ubuntu的golang-1.21軟件包版本1.21.0-1,適用于Linux/x86-64。

首先,我們需要下載并提取Ubuntu軟件包,這些軟件包是 ar(1)存檔,包含zstd壓縮的tar存檔:

$ mkdir deb
$ cd deb
$ curl -LO http://mirrors.kernel.org/ubuntu/pool/main/g/golang-1.21/golang-1.21-src_1.21.0-1_all.deb
$ ar xv golang-1.21-src_1.21.0-1_all.deb
x - debian-binary
x - control.tar.zst
x - data.tar.zst
$ unzstd < data.tar.zst | tar xv
...
x ./usr/share/go-1.21/src/archive/tar/common.go
x ./usr/share/go-1.21/src/archive/tar/example_test.go
x ./usr/share/go-1.21/src/archive/tar/format.go
x ./usr/share/go-1.21/src/archive/tar/fuzz_test.go
...
$

那是源代碼存檔?,F(xiàn)在是amd64二進制存檔:

$ rm -f debian-binary *.zst
$ curl -LO http://mirrors.kernel.org/ubuntu/pool/main/g/golang-1.21/golang-1.21-go_1.21.0-1_amd64.deb
$ ar xv golang-1.21-src_1.21.0-1_all.deb
x - debian-binary
x - control.tar.zst
x - data.tar.zst
$ unzstd < data.tar.zst | tar xv | grep -v '/$'
...
x ./usr/lib/go-1.21/bin/go
x ./usr/lib/go-1.21/bin/gofmt
x ./usr/lib/go-1.21/go.env
x ./usr/lib/go-1.21/pkg/tool/linux_amd64/addr2line
x ./usr/lib/go-1.21/pkg/tool/linux_amd64/asm
x ./usr/lib/go-1.21/pkg/tool/linux_amd64/buildid
...
$

Ubuntu將普通的Go樹拆分成兩半,分別位于/usr/share/go-1.21和/usr/lib/go-1.21。讓我們將它們重新組合在一起:

$ mkdir go-ubuntu
$ cp -R usr/share/go-1.21/* usr/lib/go-1.21/* go-ubuntu
cp: cannot overwrite directory go-ubuntu/api with non-directory usr/lib/go-1.21/api
cp: cannot overwrite directory go-ubuntu/misc with non-directory usr/lib/go-1.21/misc
cp: cannot overwrite directory go-ubuntu/pkg/include with non-directory usr/lib/go-1.21/pkg/include
cp: cannot overwrite directory go-ubuntu/src with non-directory usr/lib/go-1.21/src
cp: cannot overwrite directory go-ubuntu/test with non-directory usr/lib/go-1.21/test
$

這些錯誤只是復制符號鏈接時出現(xiàn)的,我們可以忽略它們。

現(xiàn)在我們需要下載并提取上游的Go源代碼:

$ curl -LO https://go.googlesource.com/go/+archive/refs/tags/go1.21.0.tar.gz
$ mkdir go-clean
$ cd go-clean
$ curl -L https://go.googlesource.com/go/+archive/refs/tags/go1.21.0.tar.gz | tar xzv
...
x src/archive/tar/common.go
x src/archive/tar/example_test.go
x src/archive/tar/format.go
x src/archive/tar/fuzz_test.go
...
$

為了避免一些嘗試和錯誤,結(jié)果表明Ubuntu使用 GO386=softfloat 構(gòu)建Go,這會在為32位x86編譯時強制使用軟浮點,并剝離(從生成的ELF二進制文件中刪除符號表)?,F(xiàn)在我們從 GO386=softfloat 構(gòu)建開始:

$ cd src
$ GOOS=linux GO386=softfloat ./make.bash -distpack
Building Go cmd/dist using /Users/rsc/sdk/go1.17.13. (go1.17.13 darwin/amd64)
Building Go toolchain1 using /Users/rsc/sdk/go1.17.13.
Building Go bootstrap cmd/go (go_bootstrap) using Go toolchain1.
Building Go toolchain2 using go_bootstrap and Go toolchain1.
Building Go toolchain3 using go_bootstrap and Go toolchain2.
Building commands for host, darwin/amd64.
Building packages and commands for target, linux/amd64.
Packaging archives for linux/amd64.
distpack: 818d46ede85682dd go1.21.0.src.tar.gz
distpack: 4fcd8651d084a03d go1.21.0.linux-amd64.tar.gz
distpack: eab8ed80024f444f v0.0.1-go1.21.0.linux-amd64.zip
distpack: 58528cce1848ddf4 v0.0.1-go1.21.0.linux-amd64.mod
distpack: d8da1f27296edea4 v0.0.1-go1.21.0.linux-amd64.info
---
Installed Go for linux/amd64 in /Users/rsc/deb/go-clean
Installed commands in /Users/rsc/deb/go-clean/bin
*** You need to add /Users/rsc/deb/go-clean/bin to your PATH.
$

這將標準包留在了 pkg/distpack/go1.21.0.linux-amd64.tar.gz 中。讓我們解壓它并剝離二進制文件以匹配 Ubuntu :

$ cd ../..
$ tar xzvf go-clean/pkg/distpack/go1.21.0.linux-amd64.tar.gz
x go/CONTRIBUTING.md
x go/LICENSE
x go/PATENTS
x go/README.md
x go/SECURITY.md
x go/VERSION
...
$ elfstrip go/bin/* go/pkg/tool/linux_amd64/*
$

現(xiàn)在我們可以比較我們在 Mac 上創(chuàng)建的 Go 工具鏈與 Ubuntu 提供的 Go 工具鏈之間的差異:

$ diff -r go go-ubuntu
Only in go: CONTRIBUTING.md
Only in go: LICENSE
Only in go: PATENTS
Only in go: README.md
Only in go: SECURITY.md
Only in go: codereview.cfg
Only in go: doc
Only in go: lib
Binary files go/misc/chrome/gophertool/gopher.png and go-ubuntu/misc/chrome/gophertool/gopher.png differ
Only in go-ubuntu/pkg/tool/linux_amd64: dist
Only in go-ubuntu/pkg/tool/linux_amd64: distpack
Only in go/src: all.rc
Only in go/src: clean.rc
Only in go/src: make.rc
Only in go/src: run.rc
diff -r go/src/syscall/mksyscall.pl go-ubuntu/src/syscall/mksyscall.pl
1c1
< #!/usr/bin/env perl
---
> #! /usr/bin/perl
...
$

我們成功地復制了Ubuntu軟件包的可執(zhí)行文件,并確定了剩下的完整更改集:

  • 刪除了各種元數(shù)據(jù)和支持文件。
  • 修改了 gopher.png 文件。仔細檢查后,這兩個文件是相同的,唯一的區(qū)別是嵌入的時間戳,Ubuntu 已經(jīng)更新了它。也許 Ubuntu 的打包腳本使用了重新壓縮 png 的工具,即使在不能改善現(xiàn)有壓縮的情況下,也會重新寫入時間戳。
  • 二進制文件 dist 和 distpack 是在引導過程中構(gòu)建的,但未包含在標準存檔中,但包含在 Ubuntu 軟件包中。
  • Plan 9構(gòu)建腳本(*.rc)已被刪除,盡管Windows構(gòu)建腳本(*.bat)仍然存在。
  • mksyscall.pl和其他七個未顯示的Perl腳本的頭部已更改。

特別注意的是,我們完全按位重建了工具鏈二進制文件:它們根本不顯示在差異中。也就是說,我們證明了Ubuntu的Go二進制文件與上游Go源代碼完全對應(yīng)。

更好的是,我們證明了這一點,完全不使用任何Ubuntu軟件:這些命令在Mac上運行,而unzstd和elfstrip是短小的Go程序。一個復雜的攻擊者可能會通過更改軟件包創(chuàng)建工具來將惡意代碼插入到Ubuntu軟件包中。如果他們這樣做了,使用這些惡意工具從干凈的源代碼重新生成Ubuntu軟件包仍將生成與惡意軟件包完全相同的位對位的副本。這種重新構(gòu)建方式對于這種類型的重新構(gòu)建來說是不可見的,就像Ken Thompson的編譯器攻擊一樣。不依賴于像主機操作系統(tǒng)、主機體系結(jié)構(gòu)和主機C工具鏈這樣的細節(jié)的完美可重復構(gòu)建是使這種更強的檢查成為可能的原因。

(順便提一下,為了歷史記錄,Ken Thompson曾告訴我,他的攻擊事實上已被檢測到,因為編譯器構(gòu)建停止變得可重復。它有一個漏洞:在添加到編譯器的后門中的字符串常量被不完全處理,并且每次編譯器編譯自身時都會增加一個NUL字節(jié)。最終,有人注意到了不可重復構(gòu)建,并嘗試通過編譯為匯編來找到原因。編譯器的后門在匯編輸出中根本沒有復制自己,因此匯編該輸出會刪除后門。)

結(jié)論

可重復構(gòu)建是增強開源供應(yīng)鏈的重要工具。像SLSA這樣的框架關(guān)注來源和軟件責任鏈,可以用來指導關(guān)于信任的決策。可重復構(gòu)建通過提供一種驗證信任是否恰當?shù)姆椒▉硌a充這種方法。

完美可重復性(當源文件是構(gòu)建的唯一相關(guān)輸入時)僅對能夠自行構(gòu)建的程序來說是可能的,例如編譯器工具鏈。這是一個崇高但值得追求的目標,因為自我托管的編譯器工具鏈在其他情況下很難驗證。Go的完美可重復性意味著,假設(shè)打包工具沒有修改源代碼,那么任何形式的Go 1.21.0的重新打包(替換為您喜歡的系統(tǒng))都應(yīng)該分發(fā)完全相同的二進制文件,即使它們都是從源代碼構(gòu)建的。正如我們在這篇文章中所看到的,對于Ubuntu Linux來說并不完全如此,但完美的可重復性仍然讓我們能夠使用非常不同的非Ubuntu系統(tǒng)來復制Ubuntu打包。

理想情況下,以二進制形式分發(fā)的所有開源軟件都應(yīng)具有易于復制的構(gòu)建。實際上,正如我們在本文中所看到的,不經(jīng)意的輸入很容易滲入構(gòu)建過程。對于不需要cgo的Go程序,可重復構(gòu)建就像使用CGO_ENABLED=0 go build -trimpath這樣簡單。禁用cgo會刪除主機C工具鏈作為相關(guān)輸入,而-trimpath會刪除當前目錄。如果您的程序需要cgo,您需要在運行go build之前為特定的主機C工具鏈版本做安排,比如在特定的虛擬機或容器鏡像中運行構(gòu)建。

超越Go,可重復構(gòu)建項目旨在提高所有開源軟件的可重復性,是獲取有關(guān)使您自己的軟件構(gòu)建可重復的更多信息的良好起點。


完全可復制、經(jīng)過驗證的 Go 工具鏈

聲明:本作品采用署名-非商業(yè)性使用-相同方式共享 4.0 國際 (CC BY-NC-SA 4.0)進行許可,使用時請注明出處。
Author: mengbin
blog: mengbin
Github: mengbin92
cnblogs: 戀水無意文章來源地址http://www.zghlxwxcb.cn/news/detail-695013.html


到了這里,關(guān)于完全可復制、經(jīng)過驗證的 Go 工具鏈的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務(wù),不擁有所有權(quán),不承擔相關(guān)法律責任。如若轉(zhuǎn)載,請注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實不符,請點擊違法舉報進行投訴反饋,一經(jīng)查實,立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費用

相關(guān)文章

  • python編程游戲代碼可復制,python編程游戲代碼教程

    python編程游戲代碼可復制,python編程游戲代碼教程

    大家好,小編來為大家解答以下問題,python編程游戲代碼大全,編程貓,python編程游戲代碼大全200行,今天讓我們一起來看看吧! Source code download: 本文相關(guān)源碼 大家好,給大家分享一下python編程游戲代碼大全,很多人還不知道這一點。下面詳細解釋一下python自動化運維應(yīng)用。

    2024年04月10日
    瀏覽(23)
  • Vue在頁面輸出JSON對象,測試接口可復制使用

    Vue在頁面輸出JSON對象,測試接口可復制使用

    效果圖: 數(shù)據(jù)處理前: ?數(shù)據(jù)處理后: 代碼實現(xiàn):? HTML: js:

    2024年02月13日
    瀏覽(28)
  • python游戲代碼大全可復制,python最簡單游戲代碼

    python游戲代碼大全可復制,python最簡單游戲代碼

    大家好,小編來為大家解答以下問題,python游戲編程入門游戲代碼,python游戲代碼大全可復制,現(xiàn)在讓我們一起來看看吧! 哈嘍鐵子們 表弟最近在學Python,總是跟我抱怨很枯燥無味,其實,他有沒有認真想過,可能是自己學習姿勢不對? 比方說,可以通過打游戲來學編程!

    2024年04月17日
    瀏覽(28)
  • python煙花代碼簡單可復制,python煙花代碼怎么寫

    python煙花代碼簡單可復制,python煙花代碼怎么寫

    大家好,本文將圍繞Python煙花代碼總體功能介紹展開說明,python煙花代碼簡單可復制是一個很多人都想弄明白的事情,想搞清楚python煙花代碼怎么寫需要先了解以下幾個事情。 Source code download: 本文相關(guān)源碼 疫情太嚴重了,有很多小伙伴都不能出門玩耍了。給大家看一個特別

    2024年02月20日
    瀏覽(29)
  • python煙花代碼簡單可復制,python煙花代碼怎么運行

    python煙花代碼簡單可復制,python煙花代碼怎么運行

    大家好,小編來為大家解答以下問題,Python煙花代碼總體功能介紹,python煙花代碼簡單可復制,今天讓我們一起來看看吧! 天是2023?的第9天,到了這個時間點,部分小伙伴已經(jīng)開始復盤這一年的得與失。比如今年增加了多少技能點,看了多少本書,寫了多少篇文章或者年前

    2024年01月22日
    瀏覽(42)
  • 愛心代碼編程python可復制,python有什么好玩的代碼

    愛心代碼編程python可復制,python有什么好玩的代碼

    本篇文章給大家談?wù)?0行python代碼的入門級小游戲,以及python簡單好玩的編程代碼,希望對各位有所幫助,不要忘了收藏本站喔。 大家好,我是辣條。 今天給大家?guī)?0個py小游戲,一定要收藏! 目錄 有手就行 1、吃金幣 2、打乒乓 3、滑雪 4、并夕夕版飛機大戰(zhàn) 5、打地鼠 簡

    2024年01月17日
    瀏覽(34)
  • python游戲代碼大全可復制,python小游戲代碼大全

    python游戲代碼大全可復制,python小游戲代碼大全

    大家好,本文將圍繞python游戲編程入門游戲代碼展開說明,python游戲代碼大全可復制是一個很多人都想弄明白的事情,想搞清楚python小游戲代碼大全需要先了解以下幾個事情。 本篇文章給大家談?wù)勅绾斡胮ython編寫一個簡單的小游戲,以及如何用Python做小游戲讓別人玩,希望對

    2024年04月08日
    瀏覽(23)
  • 數(shù)據(jù)結(jié)構(gòu):隊列的鏈表結(jié)構(gòu)(含完整代碼,可復制)

    1.輸出隊列 2.入隊一個元素 3.出隊一個元素 5.建立鏈表隊列 6.完整代碼

    2024年01月16日
    瀏覽(29)
  • python游戲代碼大全可復制,python超簡單小游戲代碼

    python游戲代碼大全可復制,python超簡單小游戲代碼

    大家好,小編來為大家解答以下問題,python游戲編程入門游戲代碼,python游戲代碼大全可復制,現(xiàn)在讓我們一起來看看吧! 大家好,小編為大家解答簡單的python小游戲代碼的問題。很多人還不知道python簡單的小游戲代碼,現(xiàn)在讓我們一起來看看吧! 大家好,我是小F~ 經(jīng)常聽

    2024年02月19日
    瀏覽(20)
  • python游戲代碼大全可復制,python簡單的小游戲代碼

    python游戲代碼大全可復制,python簡單的小游戲代碼

    本篇文章給大家談?wù)刾ython游戲編程入門游戲代碼,以及python游戲代碼大全可復制,希望對各位有所幫助,不要忘了收藏本站喔。 大家好,小編來為大家解答以下問題,初學者怎么用python寫簡單小游戲教程,如何用python編寫一個簡單的小游戲,今天讓我們一起來看看吧! 1、

    2024年03月20日
    瀏覽(32)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包