SafeSetID

SafeSetID 是一個 LSM 模組,它限制 setid 系列系統呼叫,以將 UID/GID 轉換從給定的 UID/GID 限制為僅系統範圍的允許列表批准的那些。這些限制還禁止給定的 UID/GID 獲取與 CAP_SET{U/G}ID 關聯的輔助許可權,例如允許使用者設定使用者名稱空間 UID/GID 對映。

背景

在沒有檔案 capabilities 的情況下,需要在 Linux 系統上切換到不同使用者的程序必須以 CAP_SETUID 許可權啟動。 CAP_SETUID 授予以 root 身份執行的程式或顯式授予 CAP_SETUID 執行時 capability 的非 root 使用者執行的程式。 通常最好使用 Linux 執行時 capabilities 而不是檔案 capabilities,因為使用檔案 capabilities 以提升的許可權執行程式會開啟可能的安全漏洞,因為任何有權訪問該檔案的使用者都可以 exec() 該程式以獲得提升的許可權。

雖然可以透過賦予完整的 CAP_SET{U/G}ID capabilities 來實現程序樹,但這通常與在非 root 使用者下執行程序樹的目標相悖。 具體來說,由於 CAP_SETUID 允許更改為系統上的任何使用者,包括 root 使用者,因此對於這種情況來說,它是一種過於強大的 capability,特別是當程式通常只調用 setuid() 來降低到許可權較低的使用者時,而不是提升許可權。 不幸的是,在 Linux 中,除了允許切換到系統上的任何使用者之外,沒有普遍可行的方法來限制使用者可以透過 setuid() 切換到的潛在 UID。 此 SafeSetID LSM 旨在提供一種以這種方式限制 setid capabilities 的解決方案。

此 LSM 的主要用例是允許非 root 程式在沒有完全 CAP_SETUID capabilities 的情況下轉換為其他不受信任的 uid。 非 root 程式仍然需要 CAP_SETUID 才能進行任何型別的轉換,但是此 LSM 施加的額外限制意味著它是 CAP_SETUID 的“更安全”版本,因為非 root 程式無法利用 CAP_SETUID 進行任何未經批准的操作(例如,setuid 到 uid 0 或建立/進入新的使用者名稱空間)。 更高階的目標是允許基於 uid 的系統服務沙箱,而不必到處都賦予 CAP_SETUID,僅僅是為了讓非 root 程式可以降低到許可權更低的 uid。 當系統上的一個非 root 守護程序應該被允許以不同的 uid 生成其他程序時,這一點尤其重要,但不希望賦予該守護程序基本上等同於 root 的 CAP_SETUID。

考慮的其他方法

在使用者空間解決此問題

對於希望具有此 LSM 中實現的受限 setid capabilities 的候選應用程式,另一種選擇是完全從應用程式中刪除 setid capabilities,並重構應用程式中的程序生成語義(例如,使用特權 helper 程式來執行程序生成和 UID/GID 轉換)。 不幸的是,圍繞程序生成存在許多語義,這些語義會受到影響,例如 fork() 呼叫,其中程式在 fork() 之後不會立即呼叫 exec(),父程序為生成的子程序指定自定義環境變數或命令列引數,或者檔案控制代碼在 fork()/exec() 之間繼承。 因此,使用使用者空間中的特權 helper 的解決方案可能不太吸引人,無法合併到依賴於 Linux 中某些程序生成語義的現有專案中。

使用使用者名稱空間

另一種可能的方法是在其自己的使用者名稱空間中執行給定的程序樹,並賦予樹中的程式 setid capabilities。 這樣,樹中的程式可以在其自身的使用者名稱空間的上下文中更改為任何所需的 UID/GID,並且只有經過批准的 UID/GID 可以映射回初始系統使用者名稱空間,從而有效地防止了許可權提升。 不幸的是,通常無法單獨使用使用者名稱空間,而不將其與其他名稱空間型別配對,這不是總是可行的。 Linux 基於“擁有”某些實體的使用者名稱空間檢查 capabilities。 例如,Linux 有網路名稱空間由建立它們的使用者的使用者名稱空間擁有的概念。 由此產生的結果是,檢查對給定網路名稱空間的訪問的 capability 是透過檢查任務在擁有網路名稱空間的使用者名稱空間的上下文中是否具有給定的 capability 來完成的,而不一定是給定任務在其下執行的使用者名稱空間。 因此,在新使用者名稱空間中生成程序實際上會阻止其訪問由初始名稱空間擁有的網路名稱空間。 對於任何期望保留 CAP_NET_ADMIN capability 以調整網路配置的應用程式來說,這是一個決定性因素。 單獨使用使用者名稱空間會導致與其他系統互動相關的問題,包括 pid 名稱空間和裝置建立的使用。

使用現有的 LSM

其他任何 in-tree LSM 都不具備限制 setid 轉換的 capability,甚至根本不使用 security_task_fix_setuid hook。 SELinux 對該 hook 說:“由於 setuid 隻影響當前程序,並且由於 SELinux 控制不基於 Linux 身份屬性,因此 SELinux 不需要控制此操作。”

使用說明

此 LSM hook setid 系統呼叫以確保如果存在適用的限制策略,則允許轉換。 策略透過 securityfs 進行配置,方法是將內容寫入 securityfs 安裝位置的 safesetid/uid_allowlist_policy 和 safesetid/gid_allowlist_policy 檔案。 新增策略的格式為“<UID>:<UID>”或“<GID>:<GID>”,使用字面數字,並以換行符結尾,例如“123:456n”。 寫入空字串“”將重新整理策略。 同樣,為 UID/GID 配置策略將阻止該 UID/GID 獲得輔助 setid 許可權,例如允許使用者設定使用者名稱空間 UID/GID 對映。

關於 GID 策略和 setgroups() 的說明

在 v5.9 中,我們正在新增對限制 CAP_SETGID 許可權的支援,就像之前對 CAP_SETUID 所做的那樣。 但是,為了與使用者空間中常見的沙箱相關程式碼約定相容,我們目前允許對具有 CAP_SETGID 限制的程序進行任意 setgroups() 呼叫。 在我們在未來的版本中新增對限制 setgroups() 呼叫的支援之前,這些 GID 策略沒有增加任何有意義的安全性。 一旦我們有了策略檢查程式碼,setgroups() 限制將被強制執行,這將依賴於 v5.9 中新增的 GID 策略配置程式碼。