xz-utils 後門的隱藏引擎:為什麼 GNU IFUNC 是安全隱患

CVE-2024-3094(xz-utils 後門)的發現,在網路安全社群中引起了巨大的震撼。雖然大部分的事後分析都集中在社交工程和軟體供應鏈的受損上,但允許後門劫持 OpenSSH 的技術機制卻經常被忽視。要理解像 xz-utils 這樣的函式庫如何能授予 SSH 伺服器 root 權限,我們必須超越惡意代碼本身,去檢視 Linux 生態系統底層的架構決策。

這項漏洞的核心在於特定發行版的補丁與 GNU C Library (glibc) 中一個強大但晦澀的功能——GNU IFUNC (Indirect Functions) 的結合。透過允許連結器 (linker) 在 main 函數開始之前執行任意代碼,IFUNC 為攻擊者提供了完美的隱蔽進入點。

依賴鏈

要理解 xz-utils 如何進入 OpenSSH 的位址空間,我們必須追溯因 Linux 發行版而異的複雜依賴網絡。

  1. OpenSSH 分離: OpenSSH 主要為 OpenBSD 開發。為了讓它在 Linux 上運行,"Portable OpenSSH" 專案提供了一套補丁。
  2. 發行版客製化: 某些發行版(特別是 Fedora 和 Debian)對 OpenSSH 進行了進一步的補丁,以將其與 systemd 整合,藉此解決 sshd 重啟期間特定的競態條件 (race conditions)。
  3. 依賴洩漏: 因為這些經過補丁的 OpenSSH 版本依賴於 libsystemd,而 libsystemd 又依賴於 xz-utils,導致 xz-utils 函式庫被載入到 SSH 守護進程 (daemon) 的記憶體空間中。

這條鏈條造成了一個關鍵漏洞:一個高權限進程 (OpenSSH) 正在載入一個包含能夠修改進程自身執行流之機制 (IFUNC) 的函式庫 (xz-utils)。

理解 GNU IFUNC

GNU IFUNC 的設計目的是允許程式在執行時決定使用哪個版本的函數。這最常用於 CPU 優化。例如,如果一個程式需要執行計算,它可以透過 IFUNC 解析器 (resolver) 來檢查當前 CPU 是否支援 AVX2 指令集;如果支援,它會回傳一個指向高度優化的 AVX2 版本函數的指標;否則,它會回傳一個指向通用實作的指標。

雖然這聽起來像是效能優化,但技術現實是,IFUNC 允許在動態連結過程中執行任意代碼。正如研究中 tty_demo.c 範例所示,IFUNC 解析器可以用來探測進程環境——例如檢查 STDOUT 是否為終端機——在程式到達其進入點之前。

為什麼 IFUNC 是安全風險

從安全角度來看,IFUNC 引入了幾個關鍵失效點:

1. 削弱 RELRO

RELRO (Relocation Read-Only) 是一種安全機制,旨在保護全域偏移量表 (Global Offset Table, GOT) 不被攻擊者覆寫。然而,IFUNC 要求 GOT 是可寫的,以便解析器可以寫入所選函數實作的位址。透過允許任意代碼在 GOT 仍為可寫狀態時執行,IFUNC 實際上在解析階段使 RELRO 失效。

2. 違反最小驚訝原則 (Principle of Least Astonishment)

大多數開發者假設載入共享函式庫只是一個被動的行為。IFUNC 違反了這一假設,因為它將載入函式庫的行為變成了一個主動的執行事件。正如一位研究員所指出的,沒有人會預期載入動態函式庫會損害旨在保護這些函式庫本身的安全性功能。

3. 複雜性與脆弱性

IFUNC 的實作與文件化非常困難。GCC 開發者先前曾將此介面描述為「錯誤」,並指出為了使其穩健而所需的 glibc 解決方案並非對所有使用者都通用,這導致了脆弱性與非預期的崩潰。

是否有更好的替代方案?

IFUNC 的批評者認為,效能增益微乎其微,且存在更安全的替代方案:

  • 全域函數指標: 開發者可以在 main 開始時,以指令式的方式解析出正確的函數實作,並將其儲存在全域指標中。雖然這些指標是可寫的,但可以使用 mprotect(2) 在初始化之後將其設為唯讀。
  • LD_PRELOAD: 對於特定的硬體目標,可以透過使用 LD_PRELOAD 環境變數的包裝腳本 (wrapper script) 來選擇正確的函式庫版本。
  • 分開的二進位檔: 提供多個針對不同 CPU 特性集優化的二進位檔,是個可行的策略,因為大多數現代 CPU 遵循可預測的特性層級(例如,任何支援 AVX-512 的 CPU 幾乎肯定也支援 SSE4.2)。

效能基準測試顯示,IFUNC 並不比使用函數指標顯著更快。在某些測試中,IFUC 實際上比單純的函數指標呼叫產生了更多的開銷,這駁斥了其存在的主要理由。

反對意見與社群討論

並非所有人都同意 IFUNC 是「罪魁禍首」。技術社群的部分成員認為,過度關注 IFUNC 是對主要失敗點的干擾:供應鏈受損。

"對於攻擊者來說,只要在常用函式庫中安裝了任意代碼,遊戲就已經輸了... IFUNC、systemd 和經過補丁的 openssh 都是與問題無關的,那僅僅是攻擊者利用其在 libxz 的立足點所採取的路徑。"

這種觀點認為,如果攻擊者沒有使用 IFUNC,他們也會找到其他方法——或許透過 C++ 全域建構子 (global constructors) 或透過修改磁碟上的二進位檔。然而,反對的論點是,透過移除 IFUNC,我們可以移除一個強大且非顯而易見的工具,這簡化了隱蔽式 rootkit 的製作。

結論

CVE-2024-3094 是一次「差點發生」的危機,凸顯了軟體供應鏈中隱性信任的危險性。雖然社交工程是主要的催化劑,但 GNU IFUNC 提供了執行攻擊所需的技術機制,使其能以優雅且隱蔽的方式進行。透過允許連結器在進程受到完全保護之前執行任意代碼,IFUNC 削弱了 Linux 執行環境的基本安全假設。為了強化生態系統,社群應考慮將 IFUNC 視為 glibc 的內部介面,並勸阻或禁用其在一般應用程式函式庫中的使用。

Sources