linux和docker的capabilities介紹

驗證環境:centos7 x86/64 內核版本4.19.9html

在linux 2.2版本以前,當內核對進程進行權限驗證的時候,能夠將進程劃分爲兩類:privileged(UID=0)和unprivilege(UID!=0)。其中privileged的進程擁有全部內核權限,而unprivileged則根據如可執行文件的權限(effective UID, effective GID,supplementary group等)進行判斷。linux

基於文件訪問的進程權限控制android

此時進程執行主要涉及6個id:Real uid/gid,Effective uid/gid/supplementary group,Saved set-user-ID/saved set-group-ID。下面以不一樣的user id爲例進行講解,group id也是相似的。git

  • supplementary group爲user的增補組,例如在添加一個名爲usetTest1的user時候,-g執行該user的primary group,-G指定該usetTest1的supplementary groups。使用id命令能夠看到「gid=」後面對應usetTest1的primary group,「groups=」後面對應usetTest1的supplementary groups。supplementary groups能夠用與DAC驗證
[root@localhost ~]# groupadd newGrp1 [root@localhost ~]# groupadd newGrp2 [root@localhost ~]# useradd -u 10000 -g root -G newGrp1,newGrp2 userTest1 [root@localhost ~]# su userTest1 [userTest@localhost root]$ id uid=10000(userTest) gid=0(root) groups=0(root),1001(newGrp1),1002(newGrp2) context=unconfined_u:unconfined_r:unconfined_t:s0-s0:c0.c1023
  • 進程的RUID爲執行該進程的用戶ID,該值一般不須要變動;當判斷一個進程是否對某個可執行文件有權限時,須要驗證EUID,在沒有開啓SUID功能時,EUID的值等於RUID;SUID主要用於設置EUID。

在上一步中建立了一個usetTest1用戶,能夠在/etc/passwd查看該用戶的home目錄,爲/home/userTest1github

userTest1:x:10000:0::/home/userTest1:/bin/bash

爲驗證SUID的功能,su切換到userTest1,並在/home/userTest1下建立一個空文件,能夠看到wr.log僅對用戶userTest1開放寫權限docker

[userTest1@localhost ~]# touch wr.log
[userTest1@localhost ~]# ll-rw-r--r--. 1 userTest1 root 10 Dec 13 18:50 wr.log

在/home/userTest1下編譯一個小程序,用於查看當前進程的RUID,EUID和SUID,並寫入wr.log。能夠看到getIds對全部用戶開發了可執行權限小程序

#include <stdio.h> #include <stdlib.h> #include <unistd.h>

int main() { uid_t ruid; uid_t euid; uid_t suid; getresuid(&ruid, &euid, &suid); printf("real_user_id=%d, effictive_user_id=%d, saved_user_id=%d\n",ruid,euid,suid); uid_t rgid; uid_t egid; uid_t sgid; getresgid(&rgid, &egid, &sgid); printf("real_group_id=%d, effictive_group_id=%d, saved_group_id=%d\n",rgid,egid,sgid); FILE *stream; stream = fopen( "wr.log", "a+" ); fprintf( stream, "%s", "hello" ); fclose( stream ); return 0; }
total 20
-rwxr-xr-x. 1 userTest1 root 8712 Dec 13 18:50 getIds -rw-r--r--. 1 userTest1 root  554 Dec 13 18:50 getIds.c -rw-r--r--. 1 userTest1 root   10 Dec 13 18:50 wr.log

在userTest1用戶下執行getIds,有以下內容,能夠看到其UID爲10000,跟建立該用戶時設置的值是同樣的,RUID=EUID;拉起該進程用戶所在的group以及該文件所屬的group都是root,因此group的數值顯示均爲0centos

[userTest1@localhost ~]$ ./getIds real_user_id=10000, effictive_user_id=10000, saved_user_id=10000 real_group_id=0, effictive_group_id=0, saved_group_id=0

在同一個host上建立不一樣組的用戶userTest2。api

[root@localhost ~]# groupadd -g 20001 newGrp3 [root@localhost home]# useradd -u 10001 -g newGrp3 userTest2

切換到用戶userTest2,並進入/home/userTest1(可能須要爲該目錄添加other的rx權限)下執行getIds,但由於wr.log的用戶和組是userTest1:root,而當前用戶是userTest2:newGrp3,所以會由於沒法打開wr.log出現段錯誤。同時也能夠看到當前進程的RUDI=EUID=10001,即建立userTest2時的UID;RGID=EGID=20001,爲建立newGrp3時的GID安全

[userTest2@localhost userTest1]$ ./getIds real_user_id=10001, effictive_user_id=10001, saved_user_id=10001 real_group_id=20001, effictive_group_id=20001, saved_group_id=20001 Segmentation fault (core dumped)

SUID的做用就是使可執行文件在不一樣用戶下能以文件擁有者的權限去執行。在userTest1用戶下爲getIds添加SUID,此時getIds文件的權限中user對應的x變爲了s

[userTest1@localhost ~]$ chmod 4755 getIds [userTest1@localhost ~]$ ll
-rwsr-xr-x. 1 userTest1 root 8712 Dec 13 18:50 getIds -rw-r--r--. 1 userTest1 root  554 Dec 13 18:50 getIds.c -rw-r--r--. 1 userTest1 root   15 Dec 13 19:02 wr.log

切換到userTest2,執行getIds,此時能夠執行成功,RUID沒有變,但EUID和SUID變爲了userTest1的值,此時EUID被SUID值爲了10000。即當前程序使用userTest1的權限(EUID)去寫入wr.log,所以不會出錯。但使用SUID是有安全風險的,本例中的程序並無能力影響除了wr.log以外的系統環境,但若是是一個包含不少功能的命令(如mount ip等),對該命令授予使用某個用戶的完整權限,很大程度上有權限泄露的風險,所以對文件設置SUID時須要謹慎。

[userTest2@localhost userTest1]$ ./getIds real_user_id=10001, effictive_user_id=10000, saved_user_id=10000 real_group_id=20001, effictive_group_id=20001, saved_group_id=20001

更多關於RUID EUID和SUID的內容參見深入理解——real user id, effective user id, saved user id in Linux

 

使用capabilities解決上述問題

在linux內核2.2版本以後將基於用戶的權限進行了劃分,稱爲capabilities,capabilities是線程相關的,使用時須要在線程上進程設置(完整的capabilities介紹參見capabilities)。那麼如何以capabilities解決上述的問題呢?一個簡單的辦法是改變wr.log的用戶和組,這樣就不會出現權限問題

對getIds.c作一個小改動增長一行修改wr.log的用戶和用戶組的操做,其中10001爲usetTest2對應的UID,20001爲userTest2對應的GID

#include <stdio.h> #include <stdlib.h> #include <unistd.h>

int main() { uid_t ruid; uid_t euid; uid_t suid; getresuid(&ruid, &euid, &suid); printf("real_user_id=%d, effictive_user_id=%d, saved_user_id=%d\n",ruid,euid,suid); uid_t rgid; uid_t egid; uid_t sgid; getresgid(&rgid, &egid, &sgid); printf("real_group_id=%d, effictive_group_id=%d, saved_group_id=%d\n",rgid,egid,sgid); chown("/home/userTest1/wr.log", 10001, 20001); FILE *stream; stream = fopen( "wr.log", "a+" ); fprintf( stream, "%s", "hello" ); fclose( stream ); return 0; }

編譯上述文件,並使用root用戶在userTest1目錄下設置getIds1擁有修改文件用戶和組的權限CAP_CHOWN,+ep表明將該權限添加到capabilities的Effective和Permitted集合中(下面介紹),

[root@localhost userTest1]# setcap cap_chown+ep getIds1

在userTest2下執行getIds能夠看到能夠執行成功,注意到wr.log的用戶和組也被修改成了userTest2的用戶和組

[userTest2@localhost userTest1]$ ./getIds1 real_user_id=10001, effictive_user_id=10001, saved_user_id=10001 real_group_id=20001, effictive_group_id=20001, saved_group_id=20001
[userTest2@localhost userTest1]$ ll -rwxrwxrwx. 1 userTest1 root 8712 Dec 13 18:50 getIds -rwxr-xr-x. 1 root root 8760 Dec 13 20:08 getIds1-rw-r--r--. 1 userTest2 newGrp3 30 Dec 13 20:09 wr.log

查看getIds的capabilities,能夠看到與設置的同樣。最終程序可以運行的原理實際上是同樣的,即程序的EUID和文件的EUID是同樣的。

[userTest2@localhost userTest1]$ getcap getIds1 getIds1 = cap_chown+ep

更簡單的辦法是給chown設置capabilities,這樣進程執行的時候會獲取chown上的capabilities,這樣就能夠擁有權限去執行。在host上執行下面命令。切換到userTest2時就可使用chown命令直接修改用戶和組。此處不能經過給bash設置cap_chow capabilities來操做,所以此時是非root用戶,bash進程在執行chown命令的時候會丟掉全部capabilities,致使缺乏capabilities而沒法運行

# setcap cap_chown=eip /bin/chown

 

    capabilities介紹

  •  capabilities能夠分爲線程capabilities和文件capabilities。
  • 線程capabilities包含如下4個capabilities集合:
  1. Effective:內核進行線程capabilities檢查時實際使用到的集合
  2. Inheritable:當程序對應的可執行文件設置了inheritable bit位時,調用execve執行該程序會繼承調用者的Inheritable集合,並將其加入到permitted集合。但在非root用戶下執行execve時,一般不會保留inheritable 集合,能夠考慮使用ambient 集合,當一個程序drop掉一個capabilities時,只能經過execve執行SUID置位的程序或者程序的文件帶有該capabilities的方式來得到該capabilities
  3. permitted:effective集合和inheritable集合的超集,限制了它們的範圍,所以若是一個capabilities不存在permitted中,是不能夠經過cap_set_proc來獲取的。當一個線程從permitted集合中丟棄一個capabilities時,只能經過獲取程序可執行文件的capabilities或execve一個set-user-ID-root(以root用戶權限運行的)程序來得到
  4. ambient :是在內核4.3以後引入的,用於補充Inheritable使用上的缺陷,ambien集合可使用函數prctl修改。當程序因爲SUID(SGID)bit位而轉變UID(GID),或執行帶有文件capabilities的程序時會致使該集合被清空

線程可使用3種方式修改capabilities:

  1. fork:子進程使用fork後會繼承父進程的capabilities
  2. cap_set_proc:直接調用系統函數修改,但須要CAP_SETPCAP capabilities權限。在內核2.6.33版本以後,禁止程序直接修改非本進程的capabilities,只容許修改調用者自身進程的capabilities(參見capset)。
  3. execve:使用該函數後的capabilities計算方式以下:
P'(ambient) = (file is privileged) ? 0 : P(ambient)
P'(permitted) = (P(inheritable) & F(inheritable)) | (F(permitted) & cap_bset) | P'(ambient) P'(effective) = F(effective) ? P'(permitted) : P'(ambient)
P'(inheritable) = P(inheritable)

P:執行前的線程capabilities
P':執行後的線程capabilities
F:文件的capabilities
privileged file指設置了capabilities或設置了SUID或SGID的文件,若是SUID或SGID被忽略,則上述轉換將不會發生

cap_bset爲bounding set,主要用來限制擁有CAP_SETPCAP權限的線程經過execve獲取文件的permitted capabilities(不影響inheritable集合),能夠看到若是cap_bset爲空,它是沒法獲取到文件的permitted集合,即(F(permitted) & cap_bset)=0。
該特性在內核2.6.25版本先後是不同的。2.6.25版本以前該特性時系統範圍內設置的(經過/proc/sys/kernal/cap-bound),2.6.25以後是線程範圍內設置的,限制了程序能夠得到的文件的capabilities。bounding集合能夠通
過繼承父進程得到,init進程在內核啓動後能夠得到全部的bounding集合,可使用prctl來減小bounding集合中的capabilities,但沒法添加新的bounding capabilities
F(effective):當一個程序以set-user-ID-root運行或者進程的EUID爲0,這類程序被稱爲capability-dumb binary,此時程序運行的文件的effective bit會被內核設置爲enable。內核在程序運行時會檢查該程序是否得到了
capability-dumb binary文件的全部permitted集合,若是沒有,返回EPREM錯誤(一般是由於文件的permitted集合被bound集合過濾致使程序沒法獲取文件的全部permitted集合)
注:根據公式,線程的permitted集合是能夠經過獲取文件capabilities擴展的
  • 文件capabilities和線程capabilities共同決定了執行execve以後線程的capabilities。設置文件capabilities須要有CAP_SETFCAP 權限。文件capabilities有以下3種:
  1. Effective:爲一個標記位,非capabilities集合。若是設置該標記位,執行execve後的新permitted集合中的capabilities都會添加到effective集合中;反之不會添加(參見上述公式中的:P'(effective) = F(effective) ? P'(permitted) : P'(ambient))。
  2. Inheritable:該集合主要是配合線程capabilities集合使用,具體使用方式參見上述公式
  3. Permitted:同上

文件的capabilities使用linux 擴展屬性來實現(extended attribute,如下簡稱EA),EA使用命名空間管理,實現方式比較簡單,即key-value方式。文件的capabilities保存在EA的security.capability中,security就是一個命名空間。使用setcap給/usr/bin/的ls目錄添加一個capabilities,加入ES,IS和PS中。

# setcap cap_net_raw=eip ls

使用getfattr能夠導出該文件對應的EA,"-m -"用於導出全部EA,"-e hex"以16進制方式導出EA

# getfattr -d -m - -e hex ls # file: ls security.capability=0x0100000200200000002000000000000000000000 security.selinux=0x73797374656d5f753a6f626a6563745f723a62696e5f743a733000

能夠看到有2個EA,security.capability對應文件的capabilities,另一個「security.selinux」主要被linux的安全模塊調用,實現強制訪問控制(MAC),固然咱們能夠是使用text方式解碼此命名空間的內容,跟使用ls -Z查看的結果是同樣的

# getfattr -d -m - -e text ls # file: ls security.capability="\000\000\000 \000\000\000 \000\000\000\000\000\000\000\000\000" security.selinux="system_u:object_r:bin_t:s0"
# ls -Z ls
-rwxr-xr-x. root root system_u:object_r:bin_t:s0       ls

上面使用hex解碼出的security.capability值爲0x0100000200200000002000000000000000000000,含5個32位的長度,即0x01000002 00200000 00200000 00000000 00000000,對應如下的結構體,注意其爲小端序,轉化爲大端序爲0x0200001 00002000 0002000 00000000 00000000

struct vfs_cap_data { __le32 magic_etc; /* Little endian */ struct { __le32 permitted; /* Little endian */ __le32 inheritable; /* Little endian */ } data[VFS_CAP_U32]; };

第一個32位數值0x0200001的最後一個bit位爲1,該bit位就是文件effective的bit位,linux/capability.h中有以下定義(參見代碼)

VFS_CAP_FLAGS_EFFECTIVE 0x000001

在設置文件的capabilities時,內核會根據capabilities的版本(版本的介紹參見capabilities)進行不一樣處理,同時也會將capabilities 版本號和effective bit位進行位或以及小端序處理(即0x02000000&0x000001=0x0200001),這樣就獲得了上面的0x0200001。內核代碼會根據長度優先處理VFS_CAP_REVISION_2的狀況,內核代碼參見L501。第三個和第四個32位數據時是同樣的,由於添加capabilities時指定了=eip,表示permitted和inheritable,一樣也是小端序,2000表明的就是cap_net_raw

#define VFS_CAP_REVISION_2 0x02000000
  • 當執行一個capability-dumb binaries(即設置了文件capabilities,但程序自己並無調用libcap庫管理這些capabilities)時,程序會嘗試獲取全部的文件capabilities,若是獲取失敗,則返回錯誤。這主要是爲了防止程序缺乏某些capabilities而沒法運行

寫一個小程序驗證上述功能,僅用於輸出一句話,編譯該文件,輸出文件名爲hello

#include<stdio.h>
int main() { printf("hello world\n"); return 0; }

爲hello添加容器中不存在的capabilities ,將該hello使用以下命令拷貝到容器中執行,會出現「sh: ./hello: Operation not permitted」的錯誤,所以hello程序會嘗試獲取文件的cap_mac_admin,但由於容器的bounding集合中不存在該capabilities而獲取失敗。

# setcap cap_mac_admin=eip hello
# docker cp hello c03a:/home
  • 當使用execve執行set-user-ID-root程序時有以下規則:
    • 當執行一個set-user-ID-root程序,或程序的RUD或EUID爲0時,則該文件的inheritable集合和permitted集合設置爲全1
    • 當執行一個set-user-ID-root程序,或程序的EUID爲0時,則該文件的effective bit設置爲1
  •  當線程在不一樣用戶之間進行轉換時,有以下規則:
    • 若是線程的RUID,EUID以及SUID的一個或多個爲0,當這些UID的值都轉變爲非0時,permitted, effective, ambient集合都會被清空
    • 當線程的EUID從0轉變爲非0,則程序的effective集合會被清空
    • 當線程的EUID從非0轉變爲0,則permitted集合會被拷貝到effective集合中若是系統用戶的UID從從非0轉變爲0(setfsuid),則以下capabilities會從effective集合中清除;當系統的EUID從非0轉變爲0,則permitted集合會被拷貝到effective集合中
    • 若是系統UID 從0轉變爲非0,則以下capabilities會被清空
CAP_CHOWN, CAP_DAC_OVERRIDE, CAP_DAC_READ_SEARCH, CAP_FOWNER, CAP_FSETID, CAP_LINUX_IMMUTABLE (since Linux 2.6.30), CAP_MAC_OVERRIDE, CAP_MKNOD
  • 當調用capset或cap_set_proc設置程序的capabilities時,必須遵照以下規則:
    • 若是調用者沒有CAP_SETPCAP ,則新的inheritable必須是現有inheritable和permitted的合集的子集
    • (Since Linux 2.6.25)新的inheritable必須是現有inheritable和bounding的合集的子集
    • 新的permitted必須是現有permitted的子集
    • 新的effective集合必須是現有permitted集合的子集

這樣也看出若是現有進程中的permitted中不存在某個capabilities,那麼即便該進程有CAP_SETPCAP權限,也不能設置該capabilities。

使用以下程序驗證上述部分功能,test.c用於設置當前線程(進程)的capabilities,調用execve執行test1,中間使用getchar()來中斷程序,用於手動查看當前程序的capabilities;test1僅查看execv執行以後的capabilities,編譯時須要加上-lcap選項,連接cap庫

test.c //調用者
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h>

#undef _POSIX_SOURCE #include <sys/capability.h> extern int errno; void listCaps(pid_t pid) { uid_t ruid; uid_t euid; uid_t suid; getresuid(&ruid, &euid, &suid); printf("real_user_id=%d, effictive_user_id=%d, saved_user_id=%d\n",ruid,euid,suid); struct __user_cap_header_struct head={_LINUX_CAPABILITY_VERSION_1, pid}; struct __user_cap_data_struct data={}; capget(&head, &data); printf("Cap data ES=0x%x, PS=0x%x, IS=0x%x\n", data.effective,data.permitted, data.inheritable); } int main(int argc, char **argv) { int error; int stat; pid_t parentPid = getpid(); cap_t caps = cap_init(); cap_value_t capList[] ={ CAP_CHOWN,CAP_DAC_OVERRIDE,CAP_SETPCAP,CAP_SETFCAP } ; unsigned num_caps = 4; cap_set_flag(caps, CAP_EFFECTIVE, num_caps, capList, CAP_SET); cap_set_flag(caps, CAP_INHERITABLE, num_caps, capList, CAP_SET); cap_set_flag(caps, CAP_PERMITTED, num_caps, capList, CAP_SET); if (cap_set_proc(caps)) { perror("capset()"); return EXIT_FAILURE; } listCaps(parentPid); getchar(); char *argv2[]={"test1",NULL}; char *envp[]={0,NULL}; error=execve("test1",argv2,envp); if(0 != error){ printf("error=%d\n",error); } parentPid = getpid(); listCaps(parentPid); cap_free(caps); return 0; }
test1.c //被調用者
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <unistd.h>

#undef _POSIX_SOURCE #include <sys/capability.h> extern int errno; void listCaps(pid_t pid) { uid_t ruid; uid_t euid; uid_t suid; getresuid(&ruid, &euid, &suid); printf("real_user_id=%d, effictive_user_id=%d, saved_user_id=%d\n",ruid,euid,suid); struct __user_cap_header_struct head={_LINUX_CAPABILITY_VERSION_1, pid}; struct __user_cap_data_struct data={}; capget(&head, &data); printf("Cap data ES=0x%x, PS=0x%x, IS=0x%x\n", data.effective,data.permitted, data.inheritable); } int main(int argc, char **argv) { int stat; pid_t parentPid = getpid(); listCaps(parentPid); return 0; }

  上述代碼編譯方式以下:

首先下載libcap源碼 wget https://git.kernel.org/pub/scm/libs/libcap/libcap.git/snapshot/libcap-2.26.tar.gz
爲驗證方便,將上述文件放在在libcap-2.26/libcap下編譯 在libcap.h中引用了一個名爲cap_names.h的頭文件,但libcap目錄下不存在,能夠直接從這拷貝

使用docker namespace中user namespace中的方式建立一個unprivileged類型的docker容器(有獨立的user namespace)

docker run -itd centos:latest /bin/sh

在host上找到對應該進程的PID,能夠在/proc/$PID/task/TID/status中查看進程的capabilities信息,截圖以下

CapInh: 00000000a80625fb CapPrm: 00000000a80625fb CapEff: 00000000a80625fb CapBnd: 00000000a80625fb

可使用capsh來解析上述值

# capsh --decode=00000000a80625fb 0x00000000a80625fb=cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_rawio,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap

當前也能夠經過容器啓動進程/bin/sh映射到host上的pid查看對應的capabilities

# getpcaps 5318 Capabilities for `5318': = cap_chown,cap_dac_override,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_net_bind_service,cap_net_raw,cap_sys_rawio,cap_sys_chroot,cap_mknod,cap_audit_write,cap_setfcap+eip

將編譯好的test和test1拷貝到建立的容器中,當前用戶ID以下

sh-4.2# id uid=0(root) gid=0(root) groups=0(root)

執行./test能夠看到以下信息,當前進程(執行execev以前)的capabilities以下,因爲進程此時因爲getchar()中斷,使用上述方式查看當前進程的信息以下(雖然該進程的全部uid爲0,但其並非一個真正系統級別的UID,其UID通過了user namespace的映射,映射到/etc/subuid)

sh-4.2# ./test real_user_id=0, effictive_user_id=0, saved_user_id=0 Cap data ES=0x80000103, PS=0x80000103, IS=0x80000103
CapInh: 0000000080000103 CapPrm: 0000000080000103 CapEff: 0000000080000103 CapBnd: 00000000a80425fb CapAmb: 0000000000000000

回車,看到完整的信息以下,雖然test1沒有設置文件capabilities,但因爲test1的uid都是0,仍然會部分遵照上述提到的"當執行一個set-user-ID-root程序,或程序進程的RUD或EUID爲0時,則該文件的inheritable集合和permitted集合設置爲全1"的規則(沒有所有繼承是受bounding集合的限制)

sh-4.2# ./test real_user_id=0, effictive_user_id=0, saved_user_id=0 Cap data ES=0x80000103, PS=0x80000103, IS=0x80000103 real_user_id=0, effictive_user_id=0, saved_user_id=0 Cap data ES=0xa80425fb, PS=0xa80425fb, IS=0x80000103
  • 當執行一個set-user-ID-root程序,或程序進程的RUD或EUID爲0時,則該文件的inheritable集合和permitted集合設置爲全1
  • 當執行一個set-user-ID-root程序,或程序進程的EUID爲0時,則該文件的effective bit設置爲1

在容器中建立一個新的用戶,其用戶和組都是captest,並修改test和test1的用戶和組

useradd captest
chown captest:captest test*
chmod 777 test*

切換到captest用戶下執行./test會返回」capset(): Operation not permitted「的錯誤,查看該用戶的進程

[captest@78d4c79e2648 home]$ ps -ef UID PID PPID C STIME TTY TIME CMD captest  204    203  0 22:37 pts/3    00:00:00 bash

查看上述bash進程的capabilities信息以下,能夠看到effective集合被清空了,這樣就致使bash下的子進程的effective集合也是空,沒有權限執行。

CapInh: 00000000a80425fb CapPrm: 0000000000000000 CapEff: 0000000000000000 CapBnd: 00000000a80425fb CapAmb: 0000000000000000

bash進程的父進程爲「su captest」,其EUID爲root,即0,該進程會執行bash命令,即EUID從0 轉變爲非0。

root      203    186  0 21:53 pts/1    00:00:00 su captest captest   204    203  0 21:53 pts/1    00:00:00 bash

上述現象符合以下規則

若是程序的RUID,EUID以及SUID的一個或多個爲0,當這些UID的值都轉變爲非0時,permitted, effective, ambient集合都會被清空 當程序的EUID從0轉變爲非0,則程序的effective集合會被清空

 在captest用戶下執行./test1,一樣也能夠看到,因爲udi所有非0,ES和PS都會清空,但IS沒有變

[captest@78d4c79e2648 home]$ ./test1 real_user_id=1001, effictive_user_id=1001, saved_user_id=1001 Cap data ES=0x0, PS=0x0, IS=0xa80425fb

在內核4.14版本的時候引入了一個Namespaced file capabilities的概念,主要用於解決VFS_CAP_REVISION_2下file capabilities沒法在不一樣user namespace下隔離的問題。在VFS_CAP_REVISION_3版本以前,一個CAP_SETFCAP進程能夠在獲取file capabilities權限的時候,並不關心該進程所在的user namespace,若是一個進程擁有CAP_SETFCAP的權限,那它能夠經過設置並執行多個file capabilities來獲取原來沒有的capabilities權限,這樣就致使進程權限徹底不受user namespace的限制且毫無心義(既然本身能夠設置capabilities並經過exec得到,那文件自身擁有的capabilities就沒用了)。所以在4.14版本新增了一個rootid的變量,rootid是指新建立的命名空間中的UID 0對應初始user namespace的UID的值,即映射到host主機上的user ID的值(/etc/subuid)

struct vfs_ns_cap_data { __le32 magic_etc; struct { __le32 permitted; /* Little endian */ __le32 inheritable; /* Little endian */ } data[VFS_CAP_U32]; __le32 rootid; };

 

    TIPS 

  • capabilities只是給了線程執行某項功能的能力,但線程是否能調用另外的程序,須要看當前線程的EUID是否與文件的EUID對應,且能獲取該文件的全部capabilities(若是文件設置了capabilities)
  • execve與fork不同,它並不會建立一個子進程,execve執行程序的時候會對capabilities進行從新計算,若是此時的程序uid不爲0或文件capabilities爲空,則該程序會失去全部的capabilities。
  • 不要輕易給一個線程或文件設置CAP_SYS_ADMIN權限,CAP_SYS_ADMIN的權限相似root
  • 在容器中給文件設置capabilities時可能會出現「Operation not permitted」的錯誤,能夠查看host的系統日誌,centos上通常時SElinux功能沒有關閉致使的,執行setenforce 0便可
  •  capabilities中定義了各個capabilities的值,但要注意這些值表明的是2的冪次方,如「#define CAP_SETPCAP 8」,則CAP_SETPCAP 的值爲2^8=256
  • docker能夠在run的時候使用--cap-add爲容器的初始進程添加capabilities,--cap-drop移除capabilities
  • 使用CAP_SETFCAP能夠爲文件設置任意capabilities,使用CAP_SETPCAP則須要按照必定的規定添加(見上文)
  • 一個線程的capabilities集合與自身設置和文件capabilities設置相關,同時也受uid的限制。
  • 切換系統用戶其實也是執行一個進程(sh或bash)

參考:

http://man7.org/linux/man-pages/man7/capabilities.7.html

https://raesene.github.io/blog/2017/08/27/Linux-capabilities-and-when-to-drop-all/

https://review.lineageos.org/c/LineageOS/android_kernel_samsung_apq8084/+/192902/6

https://github.com/riyazdf/dockercon-workshop/tree/master/capabilities

https://docs.docker.com/engine/reference/run/#runtime-privilege-and-linux-capabilities

https://s3hh.wordpress.com/2017/09/20/namespaced-file-capabilities/

相關文章
相關標籤/搜索