我正在学习linux中的posix功能和命名空间,受这些令人印象深刻的文章的启发,我写了几行代码,以更好地理解如何从不同的命名空间中看到这些功能。有些代码取自文章的示例,而不是我的剧本。。。
#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: %sn", 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: %sn", 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 %ldn", (long)pid);
fprintf(stderr, "child process capabilities %sn", cap_to_text(cap_get_proc(), NULL));
fprintf(stderr, "euid %ld, egid %ldn", (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 %sn", uidmap);
if (gidmap!=NULL) fprintf(stderr, "setting gid map %sn", 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 %sn", cap_to_text(cap_get_proc(), NULL));
fprintf(stderr, "euid %ld, egid %ldn", (long)geteuid(), (long)getegid());
}
sleep(200);
exit(0);
}
static void usage(char *pname){
fprintf(stderr, "Usage: %s -U -M mapstring -G mapstringn", pname);
fprintf(stderr, " -U use user namespacen");
fprintf(stderr, " -M uid mappingn");
fprintf(stderr, " -G gid mappingn");
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: %sn", cap_to_text(cap_get_pid(child_pid), NULL));
fprintf(stderr, "euid %ld, egid %ldn", (long)geteuid(), (long)getegid());
exit(0);
}
我证明了从新命名空间中的子进程只能将父进程的外部命名空间中的有效用户id映射到新命名空间中包括根的任何uid,但如果您试图从子进程映射不同的外部用户,则会出错。没关系。
$ ./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位也没有设置功能,有效用户也不是管理员(功能是如何存储的?数据结构如何与命名空间相关?
我浏览了一下功能代码,找出了结构。。
用户级库使用<sys/capability.h>
中定义的调用来获取功能集,特别是cappro.c中定义的所有libcap函数都使用函数capget来检索<sys/capability.h>
中定义的数据结构cap_user_header_t
和cap_user_data_t
系统调用capget在capability.c中定义,目的是用进程的能力集&header->pid
(由第一个参数传递(更新dataptr
(系统调用的第二个参数(所指向的数据结构,有一些样板代码可以将变量从内核空间复制到用户空间,反之亦然
cap_get_target_pid的键调用通过地址传递有效的、允许的、可继承的功能集。cap_get_target_pid函数通过函数task_pid_vnr
和find_task_by_vpid为参数接收的pid的pid命名空间加载任务结构。在初始检查中,它使用变量current来定义正在执行的当前任务。函数security_capget
使用LSM框架,该框架调用capget钩子cap_capget,该钩子显示了集合的检索位置。。它们保存在任务结构的凭证字段中(每个pid名称空间应该有不同的任务结构(。模块帽的挂钩在commoncap.c文件的末尾定义无论如何,我不明白为什么它不能在映射文件上写入不同的用户,如果它在父pid命名空间中设置了所有功能。仍然感到困惑。
我修改了一些测试代码,试图从新命名空间中的克隆子项中杀死它,检测到预期的权限错误
因此,我有机会深入研究内核代码,分析如何授予/拒绝kill授权
内核将要kill的进程的命名空间与当前线程的命名空间进行比较,如果它们匹配,则检查当前线程是否启用了有效的kill标志
否则(不匹配命名空间(,它会检查当前线程是否是创建要终止的进程的命名空间的进程的祖先,如果是,它允许继续评估其他linux安全模块(如果有的话(
相反,如果killer线程是目标进程的后代,并且不在进程的同一命名空间中,则kill的许可证将被拒绝。
glibc为singnal.h中定义的kill userspace调用定义了一个弱符号,所以我假设被调用的代码是在内核级别定义的,这些是涉及的系统调用:
系统调用以终止
group_send_sig_info
check_kill_permission
kill_ok_by_cred
挂钩支持lsm能力模块
我遇到了与原帖子相同的问题,似乎我通过询问libcap
的维护人员得到了答案。
看起来cap_get_pid()
使用pid
在目标进程的用户名称空间内检索功能,这意味着无论调用方是谁,如果给定的pid
相同,它都不会显示不同的结果。
这种混乱似乎是由于缺少关于libcap和用户命名空间之间交互的文档。由于与功能相比,用户名称空间是一个相对较新的概念,我认为需要更新libcap API及其文档,以了解用户名称空间与功能之间的交互。
https://bugzilla.kernel.org/show_bug.cgi?id=215812包含对API提供更好文档的请求。