Возможность из другого пространства имен пользователя

Я изучаю возможности posix и пространство имен в linux и написал несколько строк кода, вдохновленных этими впечатляющими статьями, чтобы лучше понять, как эти возможности видятся в разных пространствах имен. Какой-то фрагмент кода взят из примеров статьи, а не моей игры...

#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);
}

Я доказал, что от дочернего элемента в новом пространстве имен можно сопоставить эффективный идентификатор пользователя во внешнем пространстве имен родительского процесса только с любым идентификатором пользователя в новом пространстве имен, включая 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, ни возможности не установлены в файле, и эффективный пользователь не является администратором) Как хранятся возможности? Как структуры данных связаны с пространством имен?

3 ответа

Я посмотрел на код способности, чтобы узнать структуры..

библиотеки уровня пользователя используют вызовы, определенные в <sys/capability.h> для получения набора возможностей, в частности, все функции libcap, определенные в cappro.c, используют функцию capget для извлечения структур данных cap_user_header_t а также cap_user_data_t определяется в <sys/capability.h> Капсет системного вызова определен в файле ability.c, целью которого является обновление структуры данных, указанной dataptr (второй аргумент системного вызова) с набором возможностей процесса &header->pid (передается первым параметром), есть некоторый шаблонный код для копирования переменных из пространства ядра в пространство пользователя и наоборот.
Ключевой вызов cap_get_target_pid передает по адресу эффективные, разрешенные, наследуемые наборы возможностей. Функция cap_get_target_pid загружает структуру задачи для пространства имен pid pid, полученного аргументом, благодаря функциям task_pid_vnr и find_task_by_vpid. В начальной проверке он использует переменную current, которая определяет текущую задачу при выполнении. Функция security_capget используйте платформу LSM, которая вызывает перехват capget cap_capget, который показывает, где набор извлекается.. они сохраняются в поле учетных данных структуры задачи (для каждого пространства имен pid должна быть отдельная структура задачи). Хуки для cap модуля определены в конце файла commoncap.c. В любом случае, я опять не понимаю, почему он не может записывать в файл отображения разных пользователей, если в его пространстве имен родительского pid установлены все возможности. Все еще озадачен.

Я немного изменил тестовый код, чтобы попытаться уничтожить клонированный дочерний элемент в новом пространстве имен, обнаружив ошибку разрешения, как и ожидалось.
Поэтому у меня была возможность покопаться в коде ядра, чтобы проанализировать, как предоставляется / запрещается авторизация для уничтожения.
Ядро сравнивает пространство имен процесса для уничтожения с пространством имен текущего потока, если они совпадают с ним, проверяет, включен ли в текущем потоке флаг, эффективный для уничтожения.
В противном случае (не совпадает с пространством имен) он проверяет, является ли текущий поток предком процесса, который создал пространство имен процесса для уничтожения, если это так, он позволяет продолжить оценку других модулей безопасности Linux, если таковые имеются.
Наоборот, если поток-убийца является потомком целевого процесса и не находится в том же пространстве имен процесса, в лицензии на уничтожение отказывают.

Glibc определяет слабый символ для вызова kill userspace, определенного в singnal.h, поэтому я предполагаю, что вызываемый код определен на уровне ядра, это системные вызовы:

системный вызов, чтобы убить

group_send_sig_info

check_kill_permission

kill_ok_by_cred

подключить к способному для lsm модуль возможностей

У меня был тот же вопрос, что и в исходном сообщении, и, похоже, я получил ответ, попросив сопровождающего libcap.

Это выглядит как cap_get_pid()извлекает возможность в пользовательском пространстве имен целевого процесса с помощью , что означает, что независимо от того, кто вызывает, он не будет показывать другой результат, если заданный pidто же самое.

Путаница, по-видимому, связана с отсутствием документа по взаимодействию между libcap и пространством имен пользователя. Поскольку пространство имен пользователей является относительно новой концепцией, чем возможности, я думаю, что потребуются обновления API libcap и его документации по взаимодействию между пространствами имен пользователей и возможностями.

https://bugzilla.kernel.org/show_bug.cgi?id=215812 содержит запрос на лучшую документацию по API.

Другие вопросы по тегам