Linux Capabilities 入門教程:概念篇
- 2019 年 10 月 29 日
- 筆記
Linux 是一種安全的作業系統,它把所有的系統許可權都賦予了一個單一的 root 用戶,只給普通用戶保留有限的許可權。root 用戶擁有超級管理員許可權,可以安裝軟體、允許某些服務、管理用戶等。
作為普通用戶,如果想執行某些只有管理員才有許可權的操作,以前只有兩種辦法:一是通過 sudo 提升許可權,如果用戶很多,配置管理和許可權控制會很麻煩;二是通過 SUID(Set User ID on execution)來實現,它可以讓普通用戶允許一個 owner 為 root 的可執行文件時具有 root 的許可權。
SUID 的概念比較晦澀難懂,舉個例子就明白了,以常用的 passwd 命令為例,修改用戶密碼是需要 root 許可權的,但普通用戶卻可以通過這個命令來修改密碼,這就是因為 /bin/passwd 被設置了 SUID 標識,所以普通用戶執行 passwd 命令時,進程的 owner 就是 passwd 的所有者,也就是 root 用戶。
SUID 雖然可以解決問題,但卻帶來了安全隱患。當運行設置了 SUID 的命令時,通常只是需要很小一部分的特權,但是 SUID 給了它 root 具有的全部許可權。這些可執行文件是黑客的主要目標,如果他們發現了其中的漏洞,就很容易利用它來進行安全攻擊。簡而言之,SUID 機制增大了系統的安全攻擊面。
為了對 root 許可權進行更細粒度的控制,實現按需授權,Linux 引入了另一種機制叫 capabilities。
1. Linux capabilities 是什麼?
Capabilities 機制是在 Linux 內核 2.2 之後引入的,原理很簡單,就是將之前與超級用戶 root(UID=0)關聯的特權細分為不同的功能組,Capabilites 作為執行緒(Linux 並不真正區分進程和執行緒)的屬性存在,每個功能組都可以獨立啟用和禁用。其本質上就是將內核調用分門別類,具有相似功能的內核調用被分到同一組中。
這樣一來,許可權檢查的過程就變成了:在執行特權操作時,如果執行緒的有效身份不是 root,就去檢查其是否具有該特權操作所對應的 capabilities,並以此為依據,決定是否可以執行特權操作。
Capabilities 可以在進程執行時賦予,也可以直接從父進程繼承。所以理論上如果給 nginx 可執行文件賦予了 CAP_NET_BIND_SERVICE capabilities,那麼它就能以普通用戶運行並監聽在 80 埠上。
| capability 名稱 | 描述 |
|---|---|
| CAP_AUDIT_CONTROL | 啟用和禁用內核審計;改變審計過濾規則;檢索審計狀態和過濾規則 |
| CAP_AUDIT_READ | 允許通過 multicast netlink 套接字讀取審計日誌 |
| CAP_AUDIT_WRITE | 將記錄寫入內核審計日誌 |
| CAP_BLOCK_SUSPEND | 使用可以阻止系統掛起的特性 |
| CAP_CHOWN | 修改文件所有者的許可權 |
| CAP_DAC_OVERRIDE | 忽略文件的 DAC 訪問限制 |
| CAP_DAC_READ_SEARCH | 忽略文件讀及目錄搜索的 DAC 訪問限制 |
| CAP_FOWNER | 忽略文件屬主 ID 必須和進程用戶 ID 相匹配的限制 |
| CAP_FSETID | 允許設置文件的 setuid 位 |
| CAP_IPC_LOCK | 允許鎖定共享記憶體片段 |
| CAP_IPC_OWNER | 忽略 IPC 所有權檢查 |
| CAP_KILL | 允許對不屬於自己的進程發送訊號 |
| CAP_LEASE | 允許修改文件鎖的 FL_LEASE 標誌 |
| CAP_LINUX_IMMUTABLE | 允許修改文件的 IMMUTABLE 和 APPEND 屬性標誌 |
| CAP_MAC_ADMIN | 允許 MAC 配置或狀態更改 |
| CAP_MAC_OVERRIDE | 忽略文件的 DAC 訪問限制 |
| CAP_MKNOD | 允許使用 mknod() 系統調用 |
| CAP_NET_ADMIN | 允許執行網路管理任務 |
| CAP_NET_BIND_SERVICE | 允許綁定到小於 1024 的埠 |
| CAP_NET_BROADCAST | 允許網路廣播和多播訪問 |
| CAP_NET_RAW | 允許使用原始套接字 |
| CAP_SETGID | 允許改變進程的 GID |
| CAP_SETFCAP | 允許為文件設置任意的 capabilities |
| CAP_SETPCAP | 參考 capabilities man page |
| CAP_SETUID | 允許改變進程的 UID |
| CAP_SYS_ADMIN | 允許執行系統管理任務,如載入或卸載文件系統、設置磁碟配額等 |
| CAP_SYS_BOOT | 允許重新啟動系統 |
| CAP_SYS_CHROOT | 允許使用 chroot() 系統調用 |
| CAP_SYS_MODULE | 允許插入和刪除內核模組 |
| CAP_SYS_NICE | 允許提升優先順序及設置其他進程的優先順序 |
| CAP_SYS_PACCT | 允許執行進程的 BSD 式審計 |
| CAP_SYS_PTRACE | 允許跟蹤任何進程 |
| CAP_SYS_RAWIO | 允許直接訪問 /devport、/dev/mem、/dev/kmem 及原始塊設備 |
| CAP_SYS_RESOURCE | 忽略資源限制 |
| CAP_SYS_TIME | 允許改變系統時鐘 |
| CAP_SYS_TTY_CONFIG | 允許配置 TTY 設備 |
| CAP_SYSLOG | 允許使用 syslog() 系統調用 |
| CAP_WAKE_ALARM | 允許觸發一些能喚醒系統的東西(比如 CLOCK_BOOTTIME_ALARM 計時器) |
2. capabilities 的賦予和繼承
Linux capabilities 分為進程 capabilities 和文件 capabilities。對於進程來說,capabilities 是細分到執行緒的,即每個執行緒可以有自己的capabilities。對於文件來說,capabilities 保存在文件的擴展屬性中。
下面分別介紹執行緒(進程)的 capabilities 和文件的 capabilities。
執行緒的 capabilities
每一個執行緒,具有 5 個 capabilities 集合,每一個集合使用 64 位掩碼來表示,顯示為 16 進位格式。這 5 個 capabilities 集合分別是:
- Permitted
- Effective
- Inheritable
- Bounding
- Ambient
每個集合中都包含零個或多個 capabilities。這5個集合的具體含義如下:
Permitted
定義了執行緒能夠使用的 capabilities 的上限。它並不使能執行緒的 capabilities,而是作為一個規定。也就是說,執行緒可以通過系統調用 capset() 來從 Effective 或 Inheritable 集合中添加或刪除 capability,前提是添加或刪除的 capability 必須包含在 Permitted 集合中(其中 Bounding 集合也會有影響,具體參考下文)。 如果某個執行緒想向 Inheritable 集合中添加或刪除 capability,首先它的 Effective 集合中得包含 CAP_SETPCAP 這個 capabiliy。
Effective
內核檢查執行緒是否可以進行特權操作時,檢查的對象便是 Effective 集合。如之前所說,Permitted 集合定義了上限,執行緒可以刪除 Effective 集合中的某 capability,隨後在需要時,再從 Permitted 集合中恢復該 capability,以此達到臨時禁用 capability 的功能。
Inheritable
當執行exec() 系統調用時,能夠被新的可執行文件繼承的 capabilities,被包含在 Inheritable 集合中。這裡需要說明一下,包含在該集合中的 capabilities 並不會自動繼承給新的可執行文件,即不會添加到新執行緒的 Effective 集合中,它只會影響新執行緒的 Permitted 集合。
Bounding
Bounding 集合是 Inheritable 集合的超集,如果某個 capability 不在 Bounding 集合中,即使它在 Permitted 集合中,該執行緒也不能將該 capability 添加到它的 Inheritable 集合中。
Bounding 集合的 capabilities 在執行 fork() 系統調用時會傳遞給子進程的 Bounding 集合,並且在執行 execve 系統調用後保持不變。
- 當執行緒運行時,不能向 Bounding 集合中添加 capabilities。
- 一旦某個 capability 被從 Bounding 集合中刪除,便不能再添加回來。
- 將某個 capability 從 Bounding 集合中刪除後,如果之前
Inherited集合包含該 capability,將繼續保留。但如果後續從Inheritable集合中刪除了該 capability,便不能再添加回來。
Ambient
Linux 4.3 內核新增了一個 capabilities 集合叫 Ambient ,用來彌補 Inheritable 的不足。Ambient 具有如下特性:
Permitted和Inheritable未設置的 capabilities,Ambient也不能設置。- 當
Permitted和Inheritable關閉某許可權(比如CAP_SYS_BOOT)後,Ambient也隨之關閉對應許可權。這樣就確保了降低許可權後子進程也會降低許可權。 - 非特權用戶如果在
Permitted集合中有一個 capability,那麼可以添加到Ambient集合中,這樣它的子進程便可以在Ambient、Permitted和Effective集合中獲取這個 capability。現在不知道為什麼也沒關係,後面會通過具體的公式來告訴你。
Ambient 的好處顯而易見,舉個例子,如果你將 CAP_NET_ADMIN 添加到當前進程的 Ambient 集合中,它便可以通過 fork() 和 execve() 調用 shell 腳本來執行網路管理任務,因為 CAP_NET_ADMIN 會自動繼承下去。
文件的 capabilities
文件的 capabilities 被保存在文件的擴展屬性中。如果想修改這些屬性,需要具有 CAP_SETFCAP 的 capability。文件與執行緒的 capabilities 共同決定了通過 execve() 運行該文件後的執行緒的 capabilities。
文件的 capabilities 功能,需要文件系統的支援。如果文件系統使用了 nouuid 選項進行掛載,那麼文件的 capabilities 將會被忽略。
類似於執行緒的 capabilities,文件的 capabilities 包含了 3 個集合:
- Permitted
- Inheritable
- Effective
這3個集合的具體含義如下:
Permitted
這個集合中包含的 capabilities,在文件被執行時,會與執行緒的 Bounding 集合計算交集,然後添加到執行緒的 Permitted 集合中。
Inheritable
這個集合與執行緒的 Inheritable 集合的交集,會被添加到執行完 execve() 後的執行緒的 Permitted 集合中。
Effective
這不是一個集合,僅僅是一個標誌位。如果設置開啟,那麼在執行完 execve() 後,執行緒 Permitted 集合中的 capabilities 會自動添加到它的 Effective 集合中。對於一些舊的可執行文件,由於其不會調用 capabilities 相關函數設置自身的 Effective 集合,所以可以將可執行文件的 Effective bit 開啟,從而可以將 Permitted 集合中的 capabilities 自動添加到 Effective 集合中。
詳情請參考 Linux capabilities 的 man page。
3. 運行 execve() 後 capabilities 的變化
上面介紹了執行緒和文件的 capabilities,你們可能會覺得有些抽象難懂。下面通過具體的計算公式,來說明執行 execve() 後 capabilities 是如何被確定的。
我們用 P 代表執行 execve() 前執行緒的 capabilities,P' 代表執行 execve() 後執行緒的 capabilities,F 代表可執行文件的 capabilities。那麼:
P'(ambient) = (file is privileged) ? 0 : P(ambient)
P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding))) | P'(ambient)
P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
P'(inheritable) = P(inheritable) [i.e., unchanged]
P'(bounding) = P(bounding) [i.e., unchanged]
我們一條一條來解釋:
-
如果用戶是 root 用戶,那麼執行
execve()後執行緒的Ambient集合是空集;如果是普通用戶,那麼執行execve()後執行緒的Ambient集合將會繼承執行execve()前執行緒的Ambient集合。 - 執行
execve()前執行緒的Inheritable集合與可執行文件的Inheritable集合取交集,會被添加到執行execve()後執行緒的Permitted集合;可執行文件的 capability bounding 集合與可執行文件的Permitted集合取交集,也會被添加到執行execve()後執行緒的Permitted集合;同時執行execve()後執行緒的Ambient集合中的 capabilities 會被自動添加到該執行緒的Permitted集合中。 - 如果可執行文件開啟了 Effective 標誌位,那麼在執行完
execve()後,執行緒Permitted集合中的 capabilities 會自動添加到它的Effective集合中。 -
執行
execve()前執行緒的Inheritable集合會繼承給執行execve()後執行緒的Inheritable集合。
這裡有幾點需要著重強調:
-
上面的公式是針對系統調用
execve()的,如果是fork(),那麼子執行緒的 capabilities 資訊完全複製父進程的 capabilities 資訊。 -
可執行文件的
Inheritable集合與執行緒的Inheritable集合併沒有什麼關係,可執行文件Inheritable集合中的 capabilities 不會被添加到執行execve()後執行緒的Inheritable集合中。如果想讓新執行緒的Inheritable集合包含某個 capability,只能通過capset()將該 capability 添加到當前執行緒的Inheritable集合中(因為 P'(inheritable) = P(inheritable))。 -
如果想讓當前執行緒
Inheritable集合中的 capabilities 傳遞給新的可執行文件,該文件的Inheritable集合中也必須包含這些 capabilities(因為 P'(permitted) = (P(inheritable) & F(inheritable))|…)。 -
將當前執行緒的 capabilities 傳遞給新的可執行文件時,僅僅只是傳遞給新執行緒的
Permitted集合。如果想讓其生效,新執行緒必須通過capset()將 capabilities 添加到Effective集合中。或者開啟新的可執行文件的 Effective 標誌位(因為 P'(effective) = F(effective) ? P'(permitted) : P'(ambient))。 -
在沒有
Ambient集合之前,如果某個腳本不能調用capset(),但想讓腳本中的執行緒都能獲得該腳本的Permitted集合中的 capabilities,只能將Permitted集合中的 capabilities 添加到Inheritable集合中(P'(permitted) = P(inheritable) & F(inheritable)|…),同時開啟 Effective 標誌位(P'(effective) = F(effective) ? P'(permitted) : P'(ambient))。有 有Ambient集合之後,事情就變得簡單多了,後續的文章會詳細解釋。 -
如果某個 UID 非零(普通用戶)的執行緒執行了
execve(),那麼Permitted和Effective集合中的 capabilities 都會被清空。 -
從 root 用戶切換到普通用戶,那麼
Permitted和Effective集合中的 capabilities 都會被清空,除非設置了 SECBIT_KEEP_CAPS 或者更寬泛的 SECBIT_NO_SETUID_FIXUP。
關於上述計算公式的邏輯流程圖如下所示(不包括 Ambient 集合):

4. 簡單示例
下面我們用一個例子來演示上述公式的計算邏輯,以 ping 文件為例。如果我們將 CAP_NET_RAW capability添加到 ping 文件的 Permitted 集合中(F(Permitted)),它就會添加到執行後的執行緒的 Permitted 集合中(P'(Permitted))。由於 ping 文件具有 capabilities 意識,即能夠調用 capset() 和 capget() ,它在運行時會調用 capset() 將 CAP_NET_RAW capability 添加到執行緒的 Effective 集合中。
換句話說,如果可執行文件不具有 capabilities 意識,我們就必須要開啟 Effective 標誌位(F(Effective)),這樣就會將該 capability 自動添加到執行緒的 Effective 集合中。具有capabilities 意識的可執行文件更安全,因為它會限制執行緒使用該 capability 的時間。
我們也可以將 capabilities 添加到文件的 Inheritable 集合中,文件的 Inheritable 集合會與當前執行緒的 Inheritable 集合取交集,然後添加到新執行緒的 Permitted 集合中。這樣就可以控制可執行文件的運行環境。
看起來很有道理,但有一個問題:如果可執行文件的有效用戶是普通用戶,且沒有 Inheritable 集合,即 F(inheritable) = 0,那麼 P(inheritable) 將會被忽略(P(inheritable) & F(inheritable))。由於絕大多數可執行文件都是這種情況,因此 Inheritable 集合的可用性受到了限制。我們無法讓腳本中的執行緒自動繼承該腳本文件中的 capabilities,除非讓腳本具有 capabilities 意識。
要想改變這種狀況,可以使用 Ambient 集合。Ambient 集合會自動從父執行緒中繼承,同時會自動添加到當前執行緒的 Permitted 集合中。舉個例子,在一個 Bash 環境中(例如某個正在執行的腳本),該環境所在的執行緒的 Ambient 集合中包含 CAP_NET_RAW capability,那麼在該環境中執行 ping 文件可以正常工作,即使該文件是普通文件(沒有任何 capabilities,也沒有設置 SUID)。
5. 終極案例
最後拿 docker 舉例,如果你使用普通用戶來啟動官方的 nginx 容器,會出現以下錯誤:
bind() to 0.0.0.0:80 failed (13: Permission denied)
因為 nginx 進程的 Effective 集合中不包含 CAP_NET_BIND_SERVICE capability,且不具有 capabilities 意識(普通用戶),所以啟動失敗。要想啟動成功,至少需要將該 capability 添加到 nginx 文件的 Inheritable 集合中,同時開啟 Effective 標誌位,並且在 Kubernetes Pod 的部署清單中的 securityContext –> capabilities 欄位下面添加 NET_BIND_SERVICE(這個 capability 會被添加到 nginx 進程的 Bounding 集合中),最後還要將 capability 添加到 nginx 文件的 Permitted 集合中。如此一來就大功告成了,參考公式:P'(permitted) = ...|(F(permitted) & P(bounding)))|... 和 P'(effective) = F(effective) ? P'(permitted) : P'(ambient)。
如果容器開啟了 securityContext/allowPrivilegeEscalation,上述設置仍然可以生效。如果 nginx 文件具有 capabilities 意識,那麼只需要將 CAP_NET_BIND_SERVICE capability 添加到它的 Inheritable 集合中就可以正常工作了。
當然了,除了上述使用文件擴展屬性的方法外,還可以使用 Ambient 集合來讓非 root 容器進程正常工作,但 Kubernetes 目前還不支援這個屬性,具體參考 Kubernetes 項目的 issue。
雖然 Kubernetes 官方不支援,但我們可以自己來實現,具體實現方式可以關注我後續的文章。
6. 參考資料
- Linux Capabilities: Why They Exist and How They Work
- Understanding Capabilities in Linux
- Linux Capabilities in a nutshell
- Linux的capabilities機制
微信公眾號
掃一掃下面的二維碼關注微信公眾號,在公眾號中回復◉加群◉即可加入我們的雲原生交流群,和孫宏亮、張館長、陽明等大佬一起探討雲原生技術



