来自不同用户名称空间的功能

时间:2018-12-04 01:41:31

标签: posix capability linux-namespaces

我正在研究linux中的posix功能和名称空间,我写了一些受这些令人印象深刻的articles启发的代码行,以更好地理解如何从不同的名称空间看到这些功能。某些代码取材于本文的示例,而不是我的剧本...

#define _GNU_SOURCE
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <sched.h>
#include <sys/capability.h>
#include "caputilities.h"


#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                        } while (0)
#define MAXLEN 255

/* Replace commas in mapping string arguments with newlines */
static void get_mapstr(char *map){
    if (map==NULL) return;
    size_t map_len = strlen(map);
    for (int j = 0; j < map_len; j++)
        if (map[j] == ',') map[j] = '\n';
}

static void save_map(char *map, char *map_file){
    int fd;
    fd = open(map_file, O_RDWR);
    if (fd == -1) {
        fprintf(stderr, "open %s: %s\n", map_file, strerror(errno));
        exit(EXIT_FAILURE);
    }
    size_t map_len = strlen(map);
    if (write(fd, map, map_len) != map_len) {
        fprintf(stderr, "write %s: %s\n", map_file, strerror(errno));
        exit(EXIT_FAILURE);
    }
    close(fd);
}

/* Start function for cloned child */
static int childFunc(void *arg){
    pid_t pid = getpid();
    fprintf(stderr, "cloned child pid %ld\n", (long)pid);
    fprintf(stderr, "child process capabilities %s\n", cap_to_text(cap_get_proc(), NULL));
    fprintf(stderr, "euid %ld, egid %ld\n", (long)geteuid(), (long)getegid());
    if (arg!=NULL){ //user ns enabled 
        char *uidmap = ((char **)arg)[0];
        char *gidmap = ((char **)arg)[1];
        if (uidmap!=NULL) fprintf(stderr, "setting uid map %s\n", uidmap);
        if (gidmap!=NULL) fprintf(stderr, "setting gid map %s\n", gidmap);
        char map_path[MAXLEN + 1];
        if (uidmap != NULL){
            snprintf(map_path, MAXLEN, "/proc/%ld/uid_map", (long)pid);
            save_map(uidmap, map_path);
        }
        if (gidmap != NULL){
            snprintf(map_path, MAXLEN, "/proc/%ld/gid_map", (long)pid);
            save_map(gidmap, map_path);
        }
        fprintf(stderr, "child process capabilities %s\n", cap_to_text(cap_get_proc(), NULL));
        fprintf(stderr, "euid %ld, egid %ld\n", (long)geteuid(), (long)getegid());
    }
    sleep(200);
    exit(0);
}

static void usage(char *pname){
    fprintf(stderr, "Usage: %s -U -M mapstring -G mapstring\n", pname);
    fprintf(stderr, "       -U use user namespace\n");
    fprintf(stderr, "       -M uid mapping\n");
    fprintf(stderr, "       -G gid mapping\n");
    fprintf(stderr, "       mapstring is a comma separated list of mapping of the form:\n");
    fprintf(stderr, "       ID_inside-ns    ID-outside-ns   length [,ID_inside-ns    ID-outside-ns   length, ...]\n");
    exit(EXIT_FAILURE);
}

#define STACK_SIZE (1024 * 1024)

static char child_stack[STACK_SIZE];    /* Space for child's stack */

/* Receive a UID and/or GID mapping as arguments
   Every mapping consists of a list of tuple (separated by new line) of the form:
       ID_inside-ns    ID-outside-ns   length
   Requiring the user to supply a string that contains newlines is
   of course inconvenient for command-line use. Thus, we permit the
   use of commas to delimit records in this string, and replace them
   with newlines before writing the string to the file. */
int main(int argc, char *argv[]){
    int flags = 0;
    char *gid_map = NULL, *uid_map = NULL;
    int opt;
    while ((opt = getopt(argc, argv, "UM:G:")) != -1) {
        switch (opt){
            case 'U': flags |= CLONE_NEWUSER;
            case 'M': uid_map = optarg; break;
            case 'G': gid_map = optarg; break;
            default: usage(argv[0]);
        }
    }
    if ((uid_map != NULL || gid_map != NULL) && !(flags & CLONE_NEWUSER)){
        fprintf(stderr,"what about give me the user namespace option? what's in your mind today?\n");
        usage(argv[0]);
    } 
    char* args[2];
    get_mapstr(uid_map); args[0] = uid_map;
    get_mapstr(gid_map); args[1] = gid_map; 
    pid_t child_pid = clone(childFunc, child_stack + STACK_SIZE, flags | SIGCHLD, (flags & CLONE_NEWUSER) ? &args : NULL);
    if (child_pid == -1) errExit("clone");
    sleep(1);
    fprintf(stderr, "child process pid capabilities from parent: %s\n", cap_to_text(cap_get_pid(child_pid), NULL));
    fprintf(stderr, "euid %ld, egid %ld\n", (long)geteuid(), (long)getegid());
    exit(0);
}

我证明从新命名空间中的子项只能将父进程的外部命名空间中的有效用户ID映射到新命名空间中的任何uid(包括root),但是如果您尝试映射其他外部用户从孩子那里得到错误。没关系。

$ ./testcap3 -U -M"1000 39 1"
cloned child pid 7659
child process capabilities = 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
euid 65534, egid 65534
setting uid map 1000 39 1
write /proc/7659/uid_map: Operation not permitted
child process pid capabilities from parent: = 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
euid 1000, egid 1000
$ ./testcap3 -U -M"0 1000 1"
cloned child pid 7665
child process capabilities = 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
euid 65534, egid 65534
setting uid map 0 1000 1
child process capabilities = 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
euid 0, egid 65534
child process pid capabilities from parent: = 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
euid 1000, egid 1000

我不明白为什么从父进程打印时子进程的功能显示为全部启用。 我本来希望在外部名称空间中看不到特权,我错了吗? 显然,二进制testcap3没有特权(文件上既未设置setuid / setgid位,也未设置功能,并且有效用户不是管理员) 功能如何存储?数据结构如何与名称空间关联?

2 个答案:

答案 0 :(得分:0)

我浏览了功能代码以找出结构。

用户级库使用<sys/capability.h>中定义的调用来获取功能集,特别是cappro.c中定义的所有libcap函数都利用函数capget来检索数据结构cap_user_header_tcap_user_data_t中定义的<sys/capability.h> 系统调用Capget的定义是在capability.c,目的是使用进程dataptr的功能集(由第一个传递的)来更新&header->pid(系统调用的第二个参数)指向的数据结构。参数),有一些样板代码可以将变量从内核空间复制到用户空间,反之亦然。.
cap_get_target_pid的键调用按地址传递有效,允许,可继承的功能集。 cap_get_target_pid函数通过函数task_pid_vnrfind_task_by_vpid加载参数所接收的pid的pid名称空间的任务结构。 在初始检查中,它使用变量current定义了当前正在执行的任务。 函数security_capget使用LSM框架,该框架调用capget钩子cap_capget,该钩子揭示了检索集合的位置。它们保存在任务结构的凭据字段中(对于该结构,应该有不同的任务结构每个pid名称空间)。 在文件commoncap.c的末尾定义了模块帽的钩子 无论如何,如果它在父pid名称空间中设置了所有功能,那么我仍然无法弄清为什么它不能在映射文件上写不同的用户。 仍然不解。

答案 1 :(得分:0)

我修改了一些测试代码,以尝试从新命名空间中克隆的子项中删除,从而按预期检测到权限错误。
因此,我有机会深入研究内核代码,以分析如何授予/拒绝杀死授权。
内核将要杀死的进程的名称空间与当前线程的名称空间进行比较,如果匹配,则检查当前线程是否启用了有效的杀死标志。
否则(不匹配名称空间),它将检查当前线程是否是创建要杀死的进程名称空间的进程的祖先,如果允许,则可以继续评估其他Linux安全模块(如果有)。
相反,如果杀手线程是目标进程的后代,并且不在该进程的同一个命名空间中,则将要杀死的许可证被拒绝。

glibc为singnal.h中定义的kill userspace调用定义了一个弱符号,因此我想被调用的代码是在内核级别定义的,这些是涉及到的系统调用:

syscall to kill

group_send_sig_info

check_kill_permission

kill_ok_by_cred

hook to capable for lsm capability module