連結器、載入器與目的檔格式

.dynamic 節區(the dynamic section)

當動態連結器在啟動時接手,它需要一套指示:要載入哪些函式庫、符號表在哪、去哪找重定位等等。那份指示清單住在 ELF 檔的 .dynamic 節區裡——一個精簡的標籤項目陣列,基本上是動態連結器的待辦清單與查找目錄合而為一。每個項目是一對:一個標籤說明這是哪種資訊,以及一個值(常是位址或指向字串表的偏移量)。

有幾個標籤值得記住名字。DT_NEEDED 項目指名這個物件直接依賴的共享函式庫——每個函式庫一個 DT_NEEDED,像 'libc.so.6' 與 'libssl.so.3'——動態連結器會載入它們每一個。DT_SONAME 記錄這個函式庫自己的正式名稱。DT_STRTAB 與 DT_SYMTAB 指向動態字串表與符號表。還有兩個密切相關、控制搜尋的標籤:DT_RPATH 與較新的 DT_RUNPATH 嵌入一份目錄清單,烤進二進位檔裡,是動態連結器該去尋找所需函式庫的地方。兩者的差別微妙但真實:RPATH 在 LD_LIBRARY_PATH 環境變數「之前」被查閱,而較新的 RUNPATH 在它「之後」被查閱,所以 RUNPATH 讓使用者能覆蓋烤進去的路徑,是被推薦的那個;RPATH 因此已被棄用。

它重要,是因為 .dynamic 節區是二進位檔與動態連結器之間的契約——改它就改了哪些函式庫被載入、從哪載入。像 patchelf 這樣的工具會編輯這些標籤,在事後修正二進位檔的函式庫路徑。一個常見的真實世界陷阱是 RPATH/RUNPATH 含有不安全或相對的目錄:若二進位檔在系統目錄之前搜尋一個可寫目錄,能在那裡放置惡意 .so 的攻擊者就能挾持程式。在 RUNPATH 裡使用 $ORIGIN(意思是「二進位檔所在的目錄」)是隨程式附帶自家函式庫時安全又常見的做法。

$ readelf -d app Tag Type Name/Value (NEEDED) Shared lib [libc.so.6] (NEEDED) Shared lib [libssl.so.3] (RUNPATH) Library runpath [$ORIGIN/../lib]

.dynamic 節區列出每個所需函式庫(DT_NEEDED)以及去哪搜尋它們(DT_RUNPATH,這裡透過 $ORIGIN 相對於二進位檔)。

RPATH 在 LD_LIBRARY_PATH 之前被檢查、已被棄用;RUNPATH 在它之後被檢查、較受推薦,因為使用者仍能覆蓋它。任一者中含可寫或相對的目錄都是安全漏洞——偏好以 $ORIGIN 為相對基準的絕對路徑。

又称
.dynamicDT_NEEDEDRPATHRUNPATH動態節區標籤