# 系統程式設計 - Linux Capabilities [TOC] ## References 在 4.3 版的 Linux 核心中,除了 *Permitted*, *Effective*, *Inheritable* 這三個主要的 capability set 之外,還多引入了 *Ambient* 這個 capability set。但是因為寫作時間的關係,TLPI 一書中並沒有提到 *Ambient capabilities*(第二個影片中也沒有)。這個 *Ambient capabilitis* 會導致一個行程在 `execve()` 之後的 *Permitted capabilities* 與 *Effective capabilities* 與 TLPI 一書中提到的行為稍有不同。下面第一個參考影片中則有涵蓋 *Ambient capabilities*。 除了下面的影片之外可以參考 [*Linux Capabilities: Why They Exist and How They Work*](https://blog.container-solutions.com/linux-capabilities-why-they-exist-and-how-they-work) 與 [*Linux Capabilities In Practice*](https://blog.container-solutions.com/linux-capabilities-in-practice) 這兩篇文章。而跟 Docker 有關的例子可以參考 [*Linux Capabilities and when to drop all*](https://raesene.github.io/blog/2017/08/27/Linux-capabilities-and-when-to-drop-all/) 與 [*Docker Capabilities and no-new-privileges*](https://raesene.github.io/blog/2019/06/01/docker-capabilities-and-no-new-privs/)。 ### Gerlof Langeveld - Practical use of Linux capabilities (Full Talk) , at the ORNL CentOS Dojo 這個演講中 *Gerlof Langeveld* 是用自己寫的程式來檢視行程 capabilities,但這件事其實有一個叫 [`capsh(1)`](https://www.man7.org/linux/man-pages/man1/capsh.1.html) 的命令列工具來做到。 {%youtube WYC6DHzWzFQ %} ### Linux Capabilities {%youtube YFgkNq4Mc6w %} ## 簡介 傳統的 UNIX 中,使用者可以分為兩大類:其中一類是 *super user* (也就是 UID 0),另外一類是 *normal user*。各種檔案的讀寫與執行權限,則是依照「是哪位使用者」作為依據。如果一個 *normal user* 需要 *super user* 的權限,則可以使用 [`setuid`](https://man7.org/linux/man-pages/man2/setuid.2.html) 來改變自己的 *effective user ID*,這時就可以做 *super user* 能做的所有事情。 這樣的模型雖然簡易,但並不夠細緻。一個使用者需要更多權限時,要嘛直接變成 *super user*,要嘛就什麼特權都沒有。萬一請求成為 *super user* 的行程做壞了,那麼這個行程的權限將使它能夠對系統做出很大的破壞。 這個問題的解法就是把權限進一步分為不同的細項,稱為 `capabilities(7)`。這樣需要更多權限時,就不用一次給予 *super user* 的所有權限,而是只要授權對應細項就可以了。比如說:如果只是要改變系統時間的話,給予這個行程 `CAP_SYS_TIME` 就夠了。不用因為需要改時間就讓行程具有 *super user* 的所有權限。 這時,原先判斷「檔案有沒有被設定 suid 位元」就變成判斷「檔案代表該 capability 的位元有沒有被設定」。而一個行程會不會具有某項 capability,除了看可執行檔有沒有設定該 capability 之外,也會跟這個行程「執行環境」有關。 ## 行程的 Capabilities ### 檢視行程的 Capabilities #### `/proc/PID/status` 中紀錄的 Capabilities 在 `/proc/PID/status` 檔案中名稱為 `Cap*` 形式的欄位,會紀錄 PID 為 `PID` 的行程的 capabilities。舉例來說: ```shell $ cat /proc/$$/status | grep Cap ``` 就會出現以下輸出: ``` CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 000001ffffffffff CapAmb: 0000000000000000 ``` 另外一方面,如果先變成 `root`: ```shell $ sudo su ``` 再檢視這個目錄檔案的內容: ```clike # cat /proc/$$/status | grep Cap ``` 就會發現 capabilities 有所變化 (`CapPrm` 跟 `CapEff` 的位元都變成 `1`) ``` CapInh: 0000000000000000 CapPrm: 000001ffffffffff CapEff: 000001ffffffffff CapBnd: 000001ffffffffff CapAmb: 0000000000000000 ``` 這 5 組數值各自代表一個由 capability 形成的集合。同一個數值中的不同位元代表特定的 capability 是否在這個集合中。 ### 行程的 Effective 與 Permitted 這兩種 capabilities 跟原先的狀況很接近,只不過多了一個 permitted capabilities 用來限制哪些 capabilities 可以變成 effective。 #### Effective --- 目前正在用哪些 > *This is the set of capabilities used by the kernel to perform permission checks for the thread.* 一個行程現在具有哪些 capabilities,是用來判斷一個行程當下是否具有某個 capability 的依據。就這個意義來說,effective capabilities 就類比於原先的 effective UID,是實際上判斷有沒有某項權限的依據。 #### Permitted --- 最多能用哪些 > *This is a limiting superset for the effective capabilities that the thread may assume. It is also a limiting superset for the capabilities that may be added to the inheritable set by a thread that does not have the `CAP_SETPCAP` capability in its effective set.* 「一個行程的 permitted capabilities」指得是「這個行程的 effective capabilities 最多能有什麼」。換句話說,effective capabilities 只能 permitted capabilities 的子集合。 ### 行程的 Ambient 與 Inhertible 設定可執行檔的 capabilities,可以讓執行這個可執行檔的行程得到那些 capabilities。但這樣的改變並不會自己消失。設定後的 capabilities 在沒設定回來之前,都會維持在這個可執行檔上。 現在問題來了:如果只是想暫時讓某個行程具有某個 capability,比如說想要開一個有 `CAP_NET_RAW` 這個 capability 的 `bash`。如果要為了這件事專程把 `/usr/bin/bash` 設定 capability,等到執行完之後再解除,這樣聽起來並不是很妥當的做法。因為在改回來之前,其他趁機執行 `/usr/bin/bash` 的行程都會得到這個 capability,聽起來就跟權限管理的目的互相違背。 這時候就可以借助 ambient capability 跟 inheritible capibility 這兩個機制來讓行程在「具有特定 capability」的環境下執行。 #### Ambient --- 可執行檔==沒==設定 capabilities 時,可以在 `execve` 後繼承的權限 > *This is a set of capabilities that are preserved across an `execve(2)` of a program that is not privileged.* 這個是指在執行一個==沒==設定 capabilities 的可執行檔時,在 `execve` 這個可執行檔後可以繼承的權限。這邊「繼承」的意思是「可以出現在新行程中的 permitted capabilities」當中。這邊「沒被設定 capabilities」指得不是「把 capabilities 都設成 0」,而是從頭到尾都沒有對他設定(比如說使用 `capset(2)`)。 在下面會提到:執行一個沒有被設定 capabilities 的可執行檔時,執行後的那個行程所具有的 permitted capabilities 與 effective capabilities,都會跟父行程的 ambient capabilities 一模一樣。在這個意義上,ambient capabilities 其實就是作為一個行程的執行環境的其中一部分:即使一個可執行檔沒有設定 capabilities,在一個具有 ambient capibilities 的環境下執行的話,執行時本身也可以具有 anbient capabilities 給他的 capabilities。所以這組 capabilities 叫做 ambient 也很合理。 > 在 *Gerlof Langeveld* 的演講中,投影片上的說明是寫 *preserved during program load to pass capabilities (EUID != 0)*,所以能讓父行程傳遞 capabilities 給子行程,不需藉由修改可執行檔的 capabilities。 #### Inheritable --- 可執行檔==有==設定 capabilities 時,`execve` 後可以繼承的權限 > *This is a set of capabilities preserved across an `execve(2)`. Inheritable capabilities remain inheritable when executing any program, and inheritable capabilities are added to the permitted set when executing a program that has the corresponding bits set in the file inheritable set.* 地位類似於前面的 ambient capabilities。只是這個會在可執行檔有設定 capabilities 時,才會用上。 ### 行程的 Bounding --- `execve` 時只能從可執行檔拿到的 > *During an `execve(2)`, the capability bounding set is ANDed with the file permitted capability set, and the result of this operation is assigned to the thread's permitted capability set. The capability bounding set thus places a limit on the permitted capabilities that may be granted by an executable file.* 一個行程的 *bounding capabilities* 則是在限制一個行程在 `execve()` 一個可執行檔時,只能從那個可執行檔得到哪些 capabilities: ## Capabilities 的轉移(可執行檔沒有被設定 Capabilities 時) 這個狀況類似於傳統 UNIX 的 set-UID 位元沒有被設定。 建立新行程的 `fork` 跟 `exec` 兩個階段中: 1. `fork` 之後會原封不動繼承父行程的 capabilities: > *A child created via `fork(2)` inherits copies of its parent's capability sets.* 2. `exec` 之後則有特定的規則: > *See below for a discussion of the treatment of capabilities during `execve(2)`.* 因為只有 `exec` 會改變 capabilities,所以在接下來的用語中,「新行程」、「舊行程」當中的新與舊各自是指「一個行程在 `exec` 某個可執行檔」之後與之前 --- 即使 `exec` 前後 PID 是一樣的。 ![](https://i.imgur.com/MpdXCe3.png) ### 簡介 這時候會依照 UID 與 EUID 有沒有人是 0 分成四種狀況: | | `UID != 0` | `UID == 0` | | ----------- | --------------- | ---------- | | `EUID != 0` | 狀況 1 | 狀況 2 | | `EUID == 0` | 狀況 3 | 狀況 4 | 而在這當中,狀況 1 與狀況 2 的處理是相同的,而且都是「假裝是可執行檔有設定某組 capabilities,然後用處理可執行檔具有 capabilities 的規則處理」。所以其實只有狀況 4 是真的需要討論的,而且恰好就是上面那張圖。 ### EUID 不是 0,且 UID 不是 0 (狀況 1) 照上圖的規則。這時候新的 permitted capabilities 跟舊的 *Ambient capabilities* 相同。也就是: ``` P'(permitted) = P'(ambient) ``` 而新的 effective capabilities 也是跟舊的 *Ambient capabilities* 相同: ``` P'(effective) = P'(ambient) ``` 而剩下的 ambient, inheritable, bounding capabilities 都跟原來一樣: ``` P'(ambient) = P(ambient) P'(inheritable) = P(inheritable) P'(bounding) = P(bounding) ``` ### EUID 不是 0,UID 是 0 (狀況 2) 在 `capabilities(7)` 的 **Capabilities and execution of programs by root** 章節當中: > *If the real or effective user ID of the process is 0 (root), then the file inheritable and permitted sets are ignored; instead they are notionally considered to be all ones (i.e., all capabilities enabled)* 這時候即使檔案沒有設定 capabilities,也會視為「檔案有設定 capabilities」處理。並且是將檔案的 capabilities 視為: 1. 檔案的 inheritible capabilities 與 permitted capabilities 具有所有 capabilities。 然後準用後面「檔案有設定 capabilities」的「狀況 1」處理。 ### EUID 是 0,UID 隨便 (狀況 3 與狀況 4) 上面的「視為檔案具有所有 capabilities」在這裡適用。但是除了上面提到的狀況之外,在 EUID 為 0 的時候還有更多。在 **Capabilities and execution of programs by root** 章節當中有進一步提到: > *If the effective user ID of the process is 0 (root) or the file effective bit is in fact enabled, then the file effective bit is notionally defined to be one (enabled).* 所以,在這個狀況下,除了 inheritable 與 permitted 視為全開之外,還有檔案的 effective bit 要視為 1。也就是這時候: 1. 檔案的 inheritible capabilities 與 permitted capabilities 具有所有 capabilities。且 2. 檔案的 effective bit 視為 `1` 然後準用後面「檔案有設定 capabilities」的「狀況 1」處理。 ## 檔案的 Capabilities 檔案的 capabilities 功能類似於 set-user-ID 位元。一個可執行檔上可以設定各種 capabilities,使得執行這個可執行檔的使用者可以得到這個可執行檔上設定的 capabilities。這些設定在檔案上的 capabilities ,就稱為 file capabilities。 > 因此,capabilities 的機制除了將 super user 的權限給予細分之外,對於一個可執行檔,也把傳統的「有無設定 set-user-ID 位元」改變為「有無設定特地的 capabilities」,達到粒度更細的權限管理。 而 capabilities 轉移的大原則是「原先行程的 inheritable capabilities」交集「檔案設定的 inheritable capabilities」,最後再加上「檔案設定的 permitted capabilities」。 1. 行程的 inheritable capabilities 可以在 `execve` 之後變成 permitted capabilities。但不是所有 inhertable capabilities 都能在 `execve` 之後變成 permitted capabilities,只有出現在檔案的 inheritable capabilities 上的那些 capabilities,才能在 `execve` 之後變成行程的 permitted capabilities 2. 行程可以在 `execve` 之後拿到檔案中的 permitted capabilities (所以檔案的 permitted capabilities 其實就是粒度比較細的 suid 位元)。但是不是檔案中所有的 permitted capabilities 都可以變成行程的 permitted capabilities。行程只能從檔案的 permitted capabilities 中拿自己的 bouding capabilities 有列出的那些 capabilities。 ### 檔案的 Inheritable --- 執行它的行程只能繼承原來的哪些 Capbilities > 這邊的「繼承」指得是「從 `execve` 前保留到 `execve` 後」。或者更精確地說,是「從 `execve` 前的 inhertable capabilities 變成 `execve` 後行程的 permitted capabilities」。 檔案上設定的 inferitable capabilities,會限制「原先行程的 inheritable capbilities 中,有多少會變成新行程的 permitted capabilities」,會在 `execve` 時受。只有檔案的 inheritable capabilities 出現的 capability 才可以繼承過去。 $$ \mathtt{P' = P_{inheritable}} $$ $$ \mathtt{P' \ \&=\ F_{inheritable}} $$ ### 檔案的 Permitted --- 可執行檔多給予的 Capabilities 這就像是 set-user-ID 位元那樣,只是以前是一個位元決定全有全無,現在則是每個 capability 都會有自己位元來決定。而決定一個可執行檔可以「提供」哪些 capabilities,則是由這個可執行檔上設定的 permitted capabilities 決定。 能從可執行檔拿到的權限會被行程自身的 *bounding capability set* 限制住,所以不是每個行程都可以拿到一個可執行檔上設定的所有權限。因此這時候: $$ \mathtt{P'\ |=\ (F_{permitted}\ \&\ P_{bounding})} $$ ### 檔案的 Effective Bit --- Permitted 都加到 Effective > *This is not a set, but rather just a single bit. If this bit is set, then during an `execve(2)` all of the new permitted capabilities for the thread are also raised in the effective set. If this bit is not set, then after an `execve(2)`, none of the new permitted capabilities is in the new effective set.* ### `getcap` --- 檢視檔案的 Capabilities ```shell $ getcap `which ping` /usr/bin/ping cap_net_raw=ep ``` 裡面的 `e` 表示 effetive bit 有被設定,而 `p` 表示這個 capability 有被加到檔案的 permitted capabilities 中。 ## Capabilities 的轉移(可執行檔有被設定 Capabilities 時) ![](https://i.imgur.com/96zJK92.jpg) 這時候一樣式用 EUID 與 UID 分四種狀況: | | `UID == 0` | `UID != 0` | | ----------- | --------------- | ---------- | | `EUID == 0` | 狀況 1 | 狀況 2 | | `EUID != 0` | 狀況 3 | 狀況 4 | 在這個狀況下,ambient capabilities 規定是 0。而 inheritable capabilities 與 bounding capabilities 則維持一樣: ``` P'(ambient) = 0 P'(inheritable) = P(inheritable) P'(bounding) = P(bounding) ``` 只有 permitted 跟 privileged 需要計算。而計算方式會在下面列出來。 ### 相關條款 除了 **Capabilities and execution of programs by root** 在後面一樣會用到這兩條: 1. > *If the real or effective user ID of the process is 0 (root), then the file inheritable and permitted sets are ignored; instead they are notionally considered to be all ones (i.e., all capabilities enabled)* 2. > *If the effective user ID of the process is 0 (root) or the file effective bit is in fact enabled, then the file effective bit is notionally defined to be one (enabled).* 不過,關於 UID != 0,且 EUID == 0 的狀況下,又有一些但書。 ### EUID 不是 0,且 UID 不是 0 (狀況 1) Permitted capabilities 則照上面圖片的規則計算。這個演算法可以想成下面幾步: 1. 新的 permitted capabilities 預設會是原來的 inheritable capabilities。也就是: $$ \mathtt{P'_{permitted} = P_{inheritable}} $$ 2. 如果檔案有設定 inheritable capabilities,那麼就只有檔案設定的那些 capabilities 可以在 `execve` 之後保留。因此: $$ \mathtt{P'_{permitted} \ \&=\ F_{inheritable}} $$ 3. 如果檔案有設定 permitted capabilities,那麼這個行程就可以從裡面拿「該拿的 capabilities」。這邊「該拿的 capabilities」是指「出現在 bounding capabilities 中的」那些 capabilities: $$ \mathtt{P'_{permitted}\ |=\ (F_{permitted}\ \&\ P_{bounding})} $$ 上面三步綜合起來,就會是: ``` P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & P(bounding)) ``` 最後,新的 effective capabilities 則是看檔案的 effective bit 有沒有設定。若有設定,則 effective capabilities 就跟上面的 permitted capabilities 相同; 反之則為 0。 ``` P'(effective) = F(effective) ? P'(permitted) : 0 ``` ### EUID 不是 0,且 UID 是 0 (狀況 2) 前面列的兩個條款中 1. 適用,但是 2. 不適用。所以處理方式就是: 1. 檔案的 inheritible capabilities 與 permitted capabilities 具有所有 capabilities。 然後準用狀況 1. 處理。 ### EUID 是 0,且 UID 不是 0 (狀況 3) 把檔案的 effetive bit 視為 `1`,然後準用狀況 1 處理。 > 在 `man` 的寫作脈絡中,這個狀況是寫在 **Set-user-ID-root programs that have file capabilities** 中,作為前面 2 條的但書: > > > *When such a program is executed, the process gains just the capabilities granted by the program (i.e., not all capabilities, as would occur when executing a set-user-ID-root program that does not have any associated file capabilities).* > > 也就是說:上面的 1. 與 2. 中,1. 不適用,因此就不是「忽視檔案上的 capabilities,直接把檔案 capabilities 全開」,而是「直接使用檔案上設定的 capabilities」; 而 2. 則仍然適用。 ### EUID 是 0,且 UID 是 0 (狀況 4) 跟上一個狀況一樣,但除了將檔案的 permitted capabilities 與 inheritable capabilities 直接視為全部位元都是 `1` 之外,還會把 effective bit 視為 `1`。然後按照「兩個都不是 0」的狀況處理。 1. 檔案的 inheritible capabilities 與 permitted capabilities 具有所有 capabilities。且 2. 檔案的 effective bit 視為 `1` 然後準用狀況 1. 處理。 > 所以在這個狀況下,就直接得到 bounding capabilities 中的所有 capabilities。 ## 例子 更改檔案所有權需要有 `CAP_CHOWN` 這個 capability。根據 [`chown(2)`](https://www.man7.org/linux/man-pages/man2/chown.2.html): > *Only a privileged process (Linux: one with the `CAP_CHOWN` capability) may change the owner of a file.* 所以如果新增一個檔案: ```shell $ touch random_file ``` 檢視它的所有者: ```shell $ ls -al random_file ``` ```shell -rw-rw-r-- 1 lima lima 0 Feb 16 07:17 random_file ``` 如果想要更改這個檔案的所有權的話: ```shell $ chown nobody random_file ``` 就會發現權限不足: ``` chown: changing ownership of 'random_file': Operation not permitted ``` 下面會簡單介紹 `capsh` 的功能,然後使用 `capsh` 搭配 ambient capabilities 的機制,建立一個讓新行程可以具有 `CAP_CHOWN` 這個 capability 的執行環境。 ### 例子一:`sudo` 後的 capabilities ```shell sudo capsh \ -- -c "/usr/bin/bash --norc" ``` ``` bash-5.1# cat /proc/$$/status | grep Cap CapInh: 0000000000000000 CapPrm: 000001ffffffffff CapEff: 000001ffffffffff CapBnd: 000001ffffffffff CapAmb: 0000000000000000 ``` ### 例子二:增加 inheritable capabilities ```shell sudo capsh \ --caps="cap_chown+i" \ -- -c "/usr/bin/bash --norc" ``` ``` bash-5.1# cat /proc/$$/status | grep Cap CapInh: 0000000000000001 CapPrm: 000001ffffffffff CapEff: 000001ffffffffff CapBnd: 000001ffffffffff CapAmb: 0000000000000000 ``` ```shell $ capsh --decode=0000000000000001 ``` ``` 0x0000000000000001=cap_chown ``` ### 例子三:改變使用者 ```shell $ sudo capsh \ --caps="cap_chown+i" \ --user="$USER" \ -- -c "/usr/bin/bash --norc" ``` ``` Unable to set group list for user: Operation not permitted ``` ```shell sudo capsh \ --caps="cap_chown+i cap_setpcap,cap_setuid,cap_setgid+ep"\ --user=$USER \ -- -c "/usr/bin/bash --norc" ``` ```shell bash-5.1$ cat /proc/$$/status | grep Cap CapInh: 0000000000000001 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 000001ffffffffff CapAmb: 0000000000000000 ``` ``` $ whoami lima ``` ### 例子四:Ambient Capabilities 想要開一個 shell,讓在這個 shell 中執行的程式都具有 `CAP_CHOWN`。第一眼看過去可能會想要直接把 `CAP_CHOWN` 加進 permitted 跟 effective capabilities 中: ```shell $ sudo capsh \ --caps="cap_chown+ep cap_setpcap,cap_setuid,cap_setgid+ep" --user=$USER \ -- -c "/usr/bin/bash --norc" ``` 但是這樣做的話並不會成功,因為從 `root` 切換至 `$USER` 時,permitted 與 effective capabilities 會被清空。所以這個時候去看 `proc` 裡面記錄的 capabilities,會發現: ```clike bash-5.1$ cat /proc/$$/status | grep Cap CapInh: 0000000000000000 CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 000001ffffffffff CapAmb: 0000000000000000 ``` 這時候就可以使用 ambient capabilities。因為在 ambient capabilities 中的 capabilities 必須要先在 inhertable 跟 permitted 當中,所以這邊要把 `CAP_CHOWN` 加進 inheritable 與 permitted。接著再使用 `--addamb` 選項: ```shell $ sudo capsh \ --caps="cap_chown+eip cap_setpcap,cap_setuid,cap_setgid+ep"\ --user=$USER \ --addamb=cap_chown \ -- -c "/usr/bin/bash --norc" ``` > 如果要加入 ambient capabilities 的那項 capability 沒有同時在 inheritable 跟 permitted 裡面,那就沒辦法順利新增。比如說: > > ```shell > sudo capsh \ > --caps="cap_chown+ep cap_setpcap,cap_setuid,cap_setgid+ep"\ > --user=$USER \ > --addamb=cap_chown \ > -- -c "/usr/bin/bash --norc" > ``` > > 就會出現以下錯誤: > > ``` > failed to raise ambient [cap_chown=0] > ``` 這時候如果去檢視 capabilities: ```shell bash-5.1$ cat /proc/$$/status | grep Cap ``` 會發現多了 `CAP_CHOWN`: ``` CapInh: 0000000000000001 CapPrm: 0000000000000001 CapEff: 0000000000000001 CapBnd: 000001ffffffffff CapAmb: 0000000000000001 ``` 儘管這時候不是 `root`: ```shell $ id uid=501(lima) gid=1000(lima) groups=1000(lima) ``` 但是可以更進一步做剛剛因為權限不足被拒絕的,改變檔案所有權的操作: ```shell $ chown nobody random_file ``` 並且如果去檢視這個檔案的所有者: ```shell $ ls -al random_file ``` 就會發現變成了 `nobody`: ```clike $ -rw-rw-r-- 1 nobody lima 0 Feb 16 07:17 random_file ``` 並不是只有 `chown` 這個程式會具有 `CAP_CHOWN`,比如說在這個 shell 底下執行的 `capsh --print`: ```shell bash-5.1$ capsh --print ``` 也會顯示具有 `CAP_CHOWN` (`cap_chown=eip` 那邊): ```clike Current: cap_chown=eip Bounding set = ... Ambient set =cap_chown Current IAB: ^cap_chown Securebits: 00/0x0/1'b0 secure-noroot: no (unlocked) secure-no-suid-fixup: no (unlocked) secure-keep-caps: no (unlocked) secure-no-ambient-raise: no (unlocked) uid=501(lima) euid=501(lima) gid=1000(lima) groups=1000(lima) Guessed mode: UNCERTAIN (0) ``` 這其實也符合剛剛「檔案沒有設定 capabilities」時,ambient capabilities 會直接變成新行程的 effective 與 permitted capabilities 的行為。