Linux Namespaces in operation記錄 - part 5


:本文絕大部分內容來自Linux Namespaces實踐part 5,原文系列文章詳細描述了Linux Namespace相關內容,英語過關的建議閱讀原文,本文內容主要用來記錄學習內容,如有不當之處還請評論區指正。

User Namespace隔離了安全相關的屬性,即用戶ID、組ID、根目錄、keyrings以及capabilities。一個進程的用戶ID和組ID在User Namespace內外可以是不同的。特別地,一個進程可以在User Namespace外有非零的用戶ID(普通用戶),而在User Namespace內部有爲零的用戶ID(root用戶)。

在闡述內容之前,先列舉幾個作者遇到的問題:

  1. sys/capability.h頭文件,cap_t, cap_to_text()

    Ubuntu上需要通過sudo apt-get install libcap-dev安裝庫
    連接時需加上 -lcap 選項: g++ -o a.sh test.cpp -lcap

  2. userns_child_exec.c遇到write: operation not permitted問題

    系列文章寫於2013年,之後Linux 3.19更新了gid_map文件的訪問規則,可以參考User Namespace手冊,文中與樣例Example的代碼註釋都闡述了相關問題及解決方案

創建User Namespace

通過傳遞CLONE_NEWUSER標誌給clone()unshare()函數可以創建一個User Namespace。Linux 3.8之後,創建User Namespace不在需要root權限。值得一提的是,Win10的wsl目前不支持CLONE_NEWUSER標誌,即無法創建User Namespace,在即將到來的wsl2(目前預覽版可以嚐鮮)將內置完整的Linux內核,可以提供該功能。

一個栗子

本例通過demo_userns.c展示了沒有特權的進程在新User Namespace中可以有特權。代碼如下:

int childFunc(void *arg) {
    cap_t caps;
    for (;;) {
        printf("eUID = %ld; eGID = %ld; \n", (long)geteuid(), (long)getegid());
        caps = cap_get_proc();
        printf("capabilityies: %s\n", cap_to_text(caps, NULL));
        if (arg == NULL)
            break;
        sleep(5);
    }
    return 0;
}
#define STACK_SIZE (1024 * 1024)
static char child_stack[STACK_SIZE];
int main(int argc, char *argv[]) {
    pid_t pid;
    pid = clone(childFunc, child_stack + STACK_SIZE, CLONE_NEWUSER | SIGCHLD, argv[1]);
    if (pid == -1)
        errExit("clone");
    if (waitpid(pid, NULL, 0) == -1)
        errExit("waitpid");
    return 0;
}

通過傳遞CLONE_NEWUSER標誌給clone()函數創建了新User Namespace,之後子進程childFunc在新創建的User Namespace中打印出自己的用戶ID、組ID以及capabilities。有關capability的內容參考Linux 手冊 Capability。運行上述程序得到如下結果:

eric@eric_ubuntu_server:~/ns_in_opt/part_5$ id -u
1000
eric@eric_ubuntu_server:~/ns_in_opt/part_5$ id -g
1000
eric@eric_ubuntu_server:~/ns_in_opt/part_5$ ./demo_userns
eUID = 65534; eGID = 65534;
capabilityies: = cap_chown,cap_dac_override,cap_dac_read_search,cap_fowner,cap_fsetid,cap_kill,cap_setgid,cap_setuid,cap_setpcap,cap_linux_immutable,cap_net_bind_service,cap_net_broadcast,cap_net_admin,cap_net_raw,cap_ipc_lock,cap_ipc_owner,cap_sys_module,cap_sys_rawio,cap_sys_chroot,cap_sys_ptrace,cap_sys_pacct,cap_sys_admin,cap_sys_boot,cap_sys_nice,cap_sys_resource,cap_sys_time,cap_sys_tty_config,cap_mknod,cap_lease,cap_audit_write,cap_audit_control,cap_setfcap,cap_mac_override,cap_mac_admin,cap_syslog,cap_wake_alarm,cap_block_suspend,cap_audit_read+ep

上述輸出結果展示了一些有趣的信息:

  1. capabilities: = ...表明子進程擁有所有的capability

    創建一個新User Namespace時,該Namespace中的第一個進程被授予完整的權限capability

  2. 進程的用戶和組ID在Namespace內外不同

    需要將User Namespace內的用戶與組ID映射到Namespace外部,對於沒有映射的Namespacegetuid()getgid()會返回/proc/sys/kernel/overflowuid/proc/sys/kernel/overflowgid的內容,即65534

另外,User Namespace還是可以嵌套的,即除了初始的User Namespace,每一個User Namespace都有一個父Namespace,零個或多個字Namespace

映射用戶與組ID

:如前所述,Linux 3.19對gid_map做了些更改,對於3.19及以上版本,需要先向/proc/PID/setgroups文件中寫入"deny"才能對gid_map文件進行更改。

創建User Namespace之後,映射用戶ID與組ID是必不可少的一步,這些映射可以通過向/proc/PID/uid_map/proc/PID/gid_map文件中寫入映射信息實現。映射信息的格式如下:

ID-inside-ns ID-outside-ns length

上述信息將Namespace內從ID-inside-nsID-inside-ns + length映射到了ID-outside-ns同樣長度。不妨假設ID爲/proc/PID/uid_map中PID的進程爲A,正在向/proc/PID/uid_map文件寫信息的進程爲B。ID-outside-ns取決於進程A和進程B是否在同一個User Namespace

  • 若在同一個Namespace,則ID-outside-ns即爲其父Namespace中的值
  • 若不在同一個Namespace,則ID-outside-ns的值爲進程B所在的Namespace中的us用戶或組ID。

又一個栗子

通過給demo_userns程序傳遞任意一個參數可讓其每隔幾秒輸出進程的用戶、組ID以及cap。

  1. demon_userns程序傳遞任意參數執行,如下爲輸出(前兩個)

    eric@eric_ubuntu_server:~/ns_in_opt/part_5$ ./demo_userns x
    eUID = 65534; eGID = 65534;
    capabilityies: = cap_chown,...
    eUID = 65534; eGID = 65534;
    capabilityies: = cap_chown,...
    

    可以看到,eUID與eGID(有效用戶、組ID)都爲65534

  2. 在另一個終端中執行echo '0 1000 1' > /proc/PID/uid_map命令

    eric@eric_ubuntu_server:~$ ps -C demo_userns -o 'pid uid comm'
    PID   UID COMMAND
    4738  1000 demo_userns
    4739  1000 demo_userns
    eric@eric_ubuntu_server:~$ echo '0 1000 1' > /proc/4739/uid_map
    

    即查看當前子進程的PID,之後向/proc/PID/uid_map文件中寫如映射信息。

    回到另一個終端我們看到:

    eUID = 0; eGID = 65534;
    capabilityies: = cap_chown,...
    eUID = 0; eGID = 65534;
    capabilityies: = cap_chown,...
    

    子進程在新創建的User Namespace中建立了用戶的映射,不再是默認65534

  3. 在上述相同的終端中執行如下命令

    eric@eric_ubuntu_server:~$ echo 'deny' > /proc/4739/setgroups
    eric@eric_ubuntu_server:~$ echo '0 1000 1' > /proc/4739/gid_map
    

    首先向/proc/PID/setgroups文件中寫入字符串’deny’,之後將映射信息寫入gid_map

    回到另一個終端,看到:

    eUID = 0; eGID = 0;
    capabilityies: = cap_chown,...
    capabilityies: = cap_chown,...
    

    即將子進程的User NamespaceID爲0的用戶/組映射到了父NamespaceID爲1000的用戶/組。

映射規則以及深入探索映射

關於uid_mapgid_map的映射規則不在少數,且涉及更深層次的系統細節,本文不詳細討論這些內容,感興趣的可以參考Linux user_namespace(7),建議運行其後的示例程序以獲得更直觀的感受。

另外,通過shell命令或其它方式查看用戶與組ID的映射時,得到的結果與當前進程所在的User Namespace有關,詳細信息參考上述Linux man手冊的示例程序,此處不再詳細描述。

總結

Linux中User Namespace爲非特權用戶提供了獲得特權的途徑,由於其相關細節非常之多,本文僅概括性地描述了一些常見的操作及可能遇到的問題。針對·User Namespace·的運用還要結合其它類型的·Namespace·纔可彰顯,作者也會在接下來的學習過程中進行記錄。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章