From 3d62194fec4bb4c355a290e9e8123831bf072a27 Mon Sep 17 00:00:00 2001 From: Fendy Date: Thu, 23 Jul 2026 15:19:56 +0800 Subject: [PATCH] amend gitignore --- .gitignore | 1 + Makefile | 19 ++ README.md | 134 ++++++++ container-from-scratch.c | 597 ------------------------------------ include/capabilities.h | 6 + include/cgroup.h | 9 + include/container.h | 41 +++ include/container_seccomp.h | 6 + include/hostname.h | 8 + include/mount.h | 8 + include/userns.h | 9 + src/capabilities.c | 52 ++++ src/cgroup.c | 142 +++++++++ src/hostname.c | 32 ++ src/main.c | 149 +++++++++ src/mount.c | 89 ++++++ src/seccomp.c | 50 +++ src/userns.c | 68 ++++ test-container.sh | 52 ++++ 19 files changed, 875 insertions(+), 597 deletions(-) create mode 100644 Makefile create mode 100644 README.md delete mode 100644 container-from-scratch.c create mode 100644 include/capabilities.h create mode 100644 include/cgroup.h create mode 100644 include/container.h create mode 100644 include/container_seccomp.h create mode 100644 include/hostname.h create mode 100644 include/mount.h create mode 100644 include/userns.h create mode 100644 src/capabilities.c create mode 100644 src/cgroup.c create mode 100644 src/hostname.c create mode 100644 src/main.c create mode 100644 src/mount.c create mode 100644 src/seccomp.c create mode 100644 src/userns.c create mode 100644 test-container.sh diff --git a/.gitignore b/.gitignore index d660847..30f6fd3 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,6 @@ rootfs/ +*.o *.exe *.out diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..cbf519a --- /dev/null +++ b/Makefile @@ -0,0 +1,19 @@ +CC = gcc +CFLAGS = -Wall -Werror -Iinclude +LDFLAGS = -lseccomp -lcap + +SRCS = src/main.c src/mount.c src/cgroup.c src/capabilities.c \ + src/seccomp.c src/userns.c src/hostname.c +OBJS = $(SRCS:.c=.o) +TARGET = container-from-scratch + +$(TARGET): $(OBJS) + $(CC) $(CFLAGS) $(OBJS) -o $@ $(LDFLAGS) + +src/%.o: src/%.c + $(CC) $(CFLAGS) -c $< -o $@ + +clean: + rm -f $(OBJS) $(TARGET) + +.PHONY: clean diff --git a/README.md b/README.md new file mode 100644 index 0000000..83ec5eb --- /dev/null +++ b/README.md @@ -0,0 +1,134 @@ +# Container From Scratch + +用纯 C 从零实现一个简易容器运行时,演示 Linux 容器核心原理。 + +## 功能 + +- **Namespace 隔离** — PID、UTS、NET、Mount、IPC、Cgroup、User +- **Cgroup 资源限制** — 内存 1GB、CPU shares、最大 64 进程 +- **Capability 权限收窄** — drop 20 个危险 capability +- **Seccomp 系统调用过滤** — 禁止 ptrace、keyctl 等危险 syscall + +## 依赖 + +```bash +# Alibaba Cloud Linux / RHEL +dnf install -y libseccomp-devel libcap-devel + +# Ubuntu / Debian +apt install -y libseccomp-dev libcap-dev +``` + +## 编译 + +```bash +make +``` + +清理: + +```bash +make clean +``` + +## 用法 + +``` +./container-from-scratch -u -m -c <命令> +``` + +| 参数 | 含义 | +|------|------| +| `-m` | 容器根文件系统目录(rootfs) | +| `-u` | 容器内进程的 UID | +| `-c` | 要执行的命令(必须放最后) | + +## 快速开始 + +### 1. 创建 rootfs + +```bash +mkdir -p rootfs +dnf install -y --installroot=$(pwd)/rootfs --releasever=3 bash coreutils procps-ng iproute hostname +``` + +### 2. 准备 /dev/null + +```bash +rm -f rootfs/dev/null +mknod -m 666 rootfs/dev/null c 1 3 +``` + +### 3. 运行容器 + +```bash +# 交互式 shell +./container-from-scratch -u 0 -m ./rootfs -c /bin/sh + +# 运行测试脚本 +./container-from-scratch -u 0 -m ./rootfs -c /test-container.sh +``` + +## 项目结构 + +``` +container-from-scratch/ +├── include/ +│ ├── container.h # 核心定义:child_config 结构体、系统头文件、常量 +│ ├── mount.h # mounts() — 文件系统隔离 +│ ├── cgroup.h # resources()、free_resources() — 资源限制 +│ ├── capabilities.h # capabilities() — 权限收窄 +│ ├── container_seccomp.h # syscalls() — 系统调用过滤 +│ ├── userns.h # userns()、handle_child_uid_map() — 用户命名空间 +│ └── hostname.h # choose_hostname() — 随机主机名 +├── src/ +│ ├── main.c # 主流程:参数解析、clone、waitpid +│ ├── mount.c # pivot_root、挂载 /proc、/dev +│ ├── cgroup.c # cgroup 目录创建与清理 +│ ├── capabilities.c # prctl + cap_set_flag drop 权限 +│ ├── seccomp.c # seccomp 规则定义与加载 +│ ├── userns.c # user namespace + UID/GID 映射 +│ └── hostname.c # 塔罗牌随机名生成 +├── rootfs/ # 容器根文件系统 +├── test-container.sh # 容器环境测试脚本 +├── Makefile +└── README.md +``` + +## 工作原理 + +``` +main() + ├── 解析参数(-m、-u、-c) + ├── 检查内核版本(>= 4.7) + ├── choose_hostname() → 生成随机主机名 + ├── resources() → 创建 cgroup,写入资源限制 + ├── clone() → 创建子进程(6 个 namespace flag) + │ + ├── [父进程] + │ ├── handle_child_uid_map() → 写入 uid_map/gid_map + │ └── waitpid() → 等待子进程退出 + │ + └── [子进程 — child()] + ├── sethostname() → 设置容器主机名(UTS namespace) + ├── mounts() → pivot_root + 挂载 /proc、/dev + ├── userns() → 创建 user namespace + 切换 UID + ├── capabilities() → drop 危险 capability + ├── syscalls() → 加载 seccomp 过滤器 + └── execve() → 执行目标命令 +``` + +## 容器核心机制 + +| 机制 | 作用 | 类比 | +|------|------|------| +| **Namespace** | 隔离视野(进程、网络、文件系统...) | 每个房间有独立的门牌号和家具 | +| **Cgroup** | 限制资源(内存、CPU、进程数) | 给每个房间限电限水 | +| **Capabilities** | 细分 root 权限 | 老板权限拆成"能开保险柜"、"能进机房" | +| **Seccomp** | 禁止危险系统调用 | 规定能用螺丝刀,不能用电钻 | + +四者叠加 = 容器隔离。 + +## License + +GPLv3 diff --git a/container-from-scratch.c b/container-from-scratch.c deleted file mode 100644 index 54a5362..0000000 --- a/container-from-scratch.c +++ /dev/null @@ -1,597 +0,0 @@ -/* -*- compile-command: "gcc -Wall -Werror -lcap -lseccomp contained.c -o contained" -*- */ -/* This code is licensed under the GPLv3. You can find its text here: - https://www.gnu.org/licenses/gpl-3.0.en.html */ - - -#define _GNU_SOURCE -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -struct child_config { - int argc; - uid_t uid; - int fd; - char *hostname; - char **argv; - char *mount_dir; -}; - -int capabilities() -{ - fprintf(stderr, "=> dropping capabilities..."); - int drop_caps[] = { - CAP_AUDIT_CONTROL, - CAP_AUDIT_READ, - CAP_AUDIT_WRITE, - CAP_BLOCK_SUSPEND, - CAP_DAC_READ_SEARCH, - CAP_FSETID, - CAP_IPC_LOCK, - CAP_MAC_ADMIN, - CAP_MAC_OVERRIDE, - CAP_MKNOD, - CAP_SETFCAP, - CAP_SYSLOG, - CAP_SYS_ADMIN, - CAP_SYS_BOOT, - CAP_SYS_MODULE, - CAP_SYS_NICE, - CAP_SYS_RAWIO, - CAP_SYS_RESOURCE, - CAP_SYS_TIME, - CAP_WAKE_ALARM - }; - size_t num_caps = sizeof(drop_caps) / sizeof(*drop_caps); - fprintf(stderr, "bounding..."); - for (size_t i = 0; i < num_caps; i++) { - if (prctl(PR_CAPBSET_DROP, drop_caps[i], 0, 0, 0)) { - fprintf(stderr, "prctl failed: %m\n"); - return 1; - } - } - fprintf(stderr, "inheritable..."); - cap_t caps = NULL; - if (!(caps = cap_get_proc()) - || cap_set_flag(caps, CAP_INHERITABLE, num_caps, drop_caps, CAP_CLEAR) - || cap_set_proc(caps)) { - fprintf(stderr, "failed: %m\n"); - if (caps) cap_free(caps); - return 1; - } - cap_free(caps); - fprintf(stderr, "done.\n"); - return 0; -} - -int pivot_root(const char *new_root, const char *put_old) -{ - return syscall(SYS_pivot_root, new_root, put_old); -} - -int mounts(struct child_config *config) -{ - fprintf(stderr, "=> remounting everything with MS_PRIVATE..."); - if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) { - fprintf(stderr, "failed! %m\n"); - return -1; - } - fprintf(stderr, "remounted.\n"); - - fprintf(stderr, "=> making a temp directory and a bind mount there..."); - char mount_dir[] = "/tmp/tmp.XXXXXX"; - if (!mkdtemp(mount_dir)) { - fprintf(stderr, "failed making a directory!\n"); - return -1; - } - - if (mount(config->mount_dir, mount_dir, NULL, MS_BIND | MS_PRIVATE, NULL)) { - fprintf(stderr, "bind mount failed!\n"); - return -1; - } - - char inner_mount_dir[] = "/tmp/tmp.XXXXXX/oldroot.XXXXXX"; - memcpy(inner_mount_dir, mount_dir, sizeof(mount_dir) - 1); - if (!mkdtemp(inner_mount_dir)) { - fprintf(stderr, "failed making the inner directory!\n"); - return -1; - } - fprintf(stderr, "done.\n"); - - fprintf(stderr, "=> pivoting root..."); - if (pivot_root(mount_dir, inner_mount_dir)) { - fprintf(stderr, "failed!\n"); - return -1; - } - fprintf(stderr, "done.\n"); - - char *old_root_dir = basename(inner_mount_dir); - char old_root[sizeof(inner_mount_dir) + 1] = { "/" }; - strcpy(&old_root[1], old_root_dir); - - fprintf(stderr, "=> unmounting %s...", old_root); - if (chdir("/")) { - fprintf(stderr, "chdir failed! %m\n"); - return -1; - } - if (umount2(old_root, MNT_DETACH)) { - fprintf(stderr, "umount failed! %m\n"); - return -1; - } - if (rmdir(old_root)) { - fprintf(stderr, "rmdir failed! %m\n"); - return -1; - } - fprintf(stderr, "done.\n"); - - fprintf(stderr, "=> mounting /proc..."); - if (mount("proc", "/proc", "proc", 0, NULL)) { - fprintf(stderr, "failed: %m\n"); - return -1; - } - fprintf(stderr, "done.\n"); - - fprintf(stderr, "=> mounting /dev..."); - if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,size=65536k")) { - fprintf(stderr, "failed: %m\n"); - return -1; - } - umask(0); - if (mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))) { - fprintf(stderr, "mknod null failed: %m\n"); - } - if (mknod("/dev/zero", S_IFCHR | 0666, makedev(1, 5))) { - fprintf(stderr, "mknod zero failed: %m\n"); - } - if (mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9))) { - fprintf(stderr, "mknod urandom failed: %m\n"); - } - fprintf(stderr, "done.\n"); - - return 0; -} - - -#define SCMP_FAIL SCMP_ACT_ERRNO(EPERM) - -int syscalls() -{ - scmp_filter_ctx ctx = NULL; - fprintf(stderr, "=> filtering syscalls..."); - if (!(ctx = seccomp_init(SCMP_ACT_ALLOW)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(chmod), 1, - SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISUID, S_ISUID)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(chmod), 1, - SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISGID, S_ISGID)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmod), 1, - SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISUID, S_ISUID)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmod), 1, - SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISGID, S_ISGID)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmodat), 1, - SCMP_A2(SCMP_CMP_MASKED_EQ, S_ISUID, S_ISUID)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmodat), 1, - SCMP_A2(SCMP_CMP_MASKED_EQ, S_ISGID, S_ISGID)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(unshare), 1, - SCMP_A0(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(clone), 1, - SCMP_A0(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(ioctl), 1, - SCMP_A1(SCMP_CMP_MASKED_EQ, TIOCSTI, TIOCSTI)) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(keyctl), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(add_key), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(request_key), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(ptrace), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(mbind), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(migrate_pages), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(move_pages), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(set_mempolicy), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(userfaultfd), 0) - || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(perf_event_open), 0) - || seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0) - || seccomp_load(ctx)) { - if (ctx) seccomp_release(ctx); - fprintf(stderr, "failed: %m\n"); - return 1; - } - seccomp_release(ctx); - fprintf(stderr, "done.\n"); - return 0; -} - -#define MEMORY "1073741824" -#define SHARES "256" -#define PIDS "64" -#define WEIGHT "10" -#define FD_COUNT 64 - -struct cgrp_control { - char control[256]; - struct cgrp_setting { - char name[256]; - char value[256]; - } **settings; -}; -struct cgrp_setting add_to_tasks = { - .name = "tasks", - .value = "0" -}; - -struct cgrp_control *cgrps[] = { - & (struct cgrp_control) { - .control = "memory", - .settings = (struct cgrp_setting *[]) { - & (struct cgrp_setting) { - .name = "memory.limit_in_bytes", - .value = MEMORY - }, - & (struct cgrp_setting) { - .name = "memory.kmem.limit_in_bytes", - .value = MEMORY - }, - &add_to_tasks, - NULL - } - }, - & (struct cgrp_control) { - .control = "cpu", - .settings = (struct cgrp_setting *[]) { - & (struct cgrp_setting) { - .name = "cpu.shares", - .value = SHARES - }, - &add_to_tasks, - NULL - } - }, - & (struct cgrp_control) { - .control = "pids", - .settings = (struct cgrp_setting *[]) { - & (struct cgrp_setting) { - .name = "pids.max", - .value = PIDS - }, - &add_to_tasks, - NULL - } - }, - NULL -}; -int resources(struct child_config *config) -{ - fprintf(stderr, "=> setting cgroups..."); - for (struct cgrp_control **cgrp = cgrps; *cgrp; cgrp++) { - char dir[PATH_MAX] = {0}; - fprintf(stderr, "%s...", (*cgrp)->control); - if (snprintf(dir, sizeof(dir), "/sys/fs/cgroup/%s/%s", - (*cgrp)->control, config->hostname) == -1) { - return -1; - } - if (mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR)) { - fprintf(stderr, "mkdir %s failed: %m\n", dir); - return -1; - } - for (struct cgrp_setting **setting = (*cgrp)->settings; *setting; setting++) { - char path[PATH_MAX] = {0}; - int fd = 0; - if (snprintf(path, sizeof(path), "%s/%s", dir, - (*setting)->name) == -1) { - fprintf(stderr, "snprintf failed: %m\n"); - return -1; - } - if ((fd = open(path, O_WRONLY)) == -1) { - fprintf(stderr, "opening %s failed: %m\n", path); - return -1; - } - if (write(fd, (*setting)->value, strlen((*setting)->value)) == -1) { - fprintf(stderr, "writing to %s failed: %m\n", path); - close(fd); - return -1; - } - close(fd); - } - } - fprintf(stderr, "done.\n"); - fprintf(stderr, "=> setting rlimit..."); - if (setrlimit(RLIMIT_NOFILE, - & (struct rlimit) { - .rlim_max = FD_COUNT, - .rlim_cur = FD_COUNT, - })) { - fprintf(stderr, "failed: %m\n"); - return 1; - } - fprintf(stderr, "done.\n"); - return 0; -} - -int free_resources(struct child_config *config) -{ - fprintf(stderr, "=> cleaning cgroups..."); - for (struct cgrp_control **cgrp = cgrps; *cgrp; cgrp++) { - char dir[PATH_MAX] = {0}; - char task[PATH_MAX] = {0}; - int task_fd = 0; - if (snprintf(dir, sizeof(dir), "/sys/fs/cgroup/%s/%s", - (*cgrp)->control, config->hostname) == -1 - || snprintf(task, sizeof(task), "/sys/fs/cgroup/%s/tasks", - (*cgrp)->control) == -1) { - fprintf(stderr, "snprintf failed: %m\n"); - return -1; - } - if ((task_fd = open(task, O_WRONLY)) == -1) { - fprintf(stderr, "opening %s failed: %m\n", task); - return -1; - } - if (write(task_fd, "0", 2) == -1) { - fprintf(stderr, "writing to %s failed: %m\n", task); - close(task_fd); - return -1; - } - close(task_fd); - if (rmdir(dir)) { - fprintf(stderr, "rmdir %s failed: %m", dir); - return -1; - } - } - fprintf(stderr, "done.\n"); - return 0; -} - -#define USERNS_OFFSET 10000 -#define USERNS_COUNT 2000 - -int handle_child_uid_map (pid_t child_pid, int fd) -{ - int uid_map = 0; - int has_userns = -1; - if (read(fd, &has_userns, sizeof(has_userns)) != sizeof(has_userns)) { - fprintf(stderr, "couldn't read from child!\n"); - return -1; - } - if (has_userns) { - char path[PATH_MAX] = {0}; - for (char **file = (char *[]) { "uid_map", "gid_map", 0 }; *file; file++) { - if (snprintf(path, sizeof(path), "/proc/%d/%s", child_pid, *file) - > sizeof(path)) { - fprintf(stderr, "snprintf too big? %m\n"); - return -1; - } - fprintf(stderr, "writing %s...", path); - if ((uid_map = open(path, O_WRONLY)) == -1) { - fprintf(stderr, "open failed: %m\n"); - return -1; - } - if (dprintf(uid_map, "0 %d %d\n", USERNS_OFFSET, USERNS_COUNT) == -1) { - fprintf(stderr, "dprintf failed: %m\n"); - close(uid_map); - return -1; - } - close(uid_map); - } - } - if (write(fd, & (int) { 0 }, sizeof(int)) != sizeof(int)) { - fprintf(stderr, "couldn't write: %m\n"); - return -1; - } - return 0; -} -int userns(struct child_config *config) -{ - fprintf(stderr, "=> trying a user namespace..."); - int has_userns = !unshare(CLONE_NEWUSER); - if (write(config->fd, &has_userns, sizeof(has_userns)) != sizeof(has_userns)) { - fprintf(stderr, "couldn't write: %m\n"); - return -1; - } - int result = 0; - if (read(config->fd, &result, sizeof(result)) != sizeof(result)) { - fprintf(stderr, "couldn't read: %m\n"); - return -1; - } - if (result) return -1; - if (has_userns) { - fprintf(stderr, "done.\n"); - } else { - fprintf(stderr, "unsupported? continuing.\n"); - } - fprintf(stderr, "=> switching to uid %d / gid %d...", config->uid, config->uid); - if (setgroups(1, & (gid_t) { config->uid }) || - setresgid(config->uid, config->uid, config->uid) || - setresuid(config->uid, config->uid, config->uid)) { - fprintf(stderr, "%m\n"); - return -1; - } - fprintf(stderr, "done.\n"); - return 0; -} -int child(void *arg) -{ - struct child_config *config = arg; - if (sethostname(config->hostname, strlen(config->hostname)) - || mounts(config) - || userns(config) - || capabilities() - || syscalls()) { - close(config->fd); - return -1; - } - if (close(config->fd)) { - fprintf(stderr, "close failed: %m\n"); - return -1; - } - if (execve(config->argv[0], config->argv, NULL)) { - fprintf(stderr, "execve failed! %m.\n"); - return -1; - } - return 0; -} - -int choose_hostname(char *buff, size_t len) -{ - static const char *suits[] = { "swords", "wands", "pentacles", "cups" }; - static const char *minor[] = { - "ace", "two", "three", "four", "five", "six", "seven", "eight", - "nine", "ten", "page", "knight", "queen", "king" - }; - static const char *major[] = { - "fool", "magician", "high-priestess", "empress", "emperor", - "hierophant", "lovers", "chariot", "strength", "hermit", - "wheel", "justice", "hanged-man", "death", "temperance", - "devil", "tower", "star", "moon", "sun", "judgment", "world" - }; - struct timespec now = {0}; - clock_gettime(CLOCK_MONOTONIC, &now); - size_t ix = now.tv_nsec % 78; - if (ix < sizeof(major) / sizeof(*major)) { - snprintf(buff, len, "%05lx-%s", now.tv_sec, major[ix]); - } else { - ix -= sizeof(major) / sizeof(*major); - snprintf(buff, len, - "%05lxc-%s-of-%s", - now.tv_sec, - minor[ix % (sizeof(minor) / sizeof(*minor))], - suits[ix / (sizeof(minor) / sizeof(*minor))]); - } - return 0; -} - -int main (int argc, char **argv) -{ - struct child_config config = {0}; - int err = 0; - int option = 0; - int sockets[2] = {0}; - pid_t child_pid = 0; - int last_optind = 0; - while ((option = getopt(argc, argv, "c:m:u:"))) { - switch (option) { - case 'c': - config.argc = argc - last_optind - 1; - config.argv = &argv[argc - config.argc]; - goto finish_options; - case 'm': - config.mount_dir = optarg; - break; - case 'u': - if (sscanf(optarg, "%d", &config.uid) != 1) { - fprintf(stderr, "badly-formatted uid: %s\n", optarg); - goto usage; - } - break; - default: - goto usage; - } - last_optind = optind; - } -finish_options: - if (!config.argc) goto usage; - if (!config.mount_dir) goto usage; - - fprintf(stderr, "=> validating Linux version..."); - struct utsname host = {0}; - if (uname(&host)) { - fprintf(stderr, "failed: %m\n"); - goto cleanup; - } - int major = -1; - int minor = -1; - if (sscanf(host.release, "%u.%u.", &major, &minor) != 2) { - fprintf(stderr, "weird release format: %s\n", host.release); - goto cleanup; - } - if (major < 4 || (major == 4 && minor < 7)) { - fprintf(stderr, "kernel too old: %s (need >= 4.7)\n", host.release); - goto cleanup; - } - if (strcmp("x86_64", host.machine)) { - fprintf(stderr, "expected x86_64: %s\n", host.machine); - goto cleanup; - } - fprintf(stderr, "%s on %s.\n", host.release, host.machine); - - char hostname[256] = {0}; - if (choose_hostname(hostname, sizeof(hostname))) - goto error; - config.hostname = hostname; - - if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, sockets)) { - fprintf(stderr, "socketpair failed: %m\n"); - goto error; - } - if (fcntl(sockets[0], F_SETFD, FD_CLOEXEC)) { - fprintf(stderr, "fcntl failed: %m\n"); - goto error; - } - config.fd = sockets[1]; - #define STACK_SIZE (1024 * 1024) - - char *stack = 0; - if (!(stack = malloc(STACK_SIZE))) { - fprintf(stderr, "=> malloc failed, out of memory?\n"); - goto error; - } - if (resources(&config)) { - err = 1; - goto clear_resources; - } - int flags = CLONE_NEWNS - | CLONE_NEWCGROUP - | CLONE_NEWPID - | CLONE_NEWIPC - | CLONE_NEWNET - | CLONE_NEWUTS; - if ((child_pid = clone(child, stack + STACK_SIZE, flags | SIGCHLD, &config)) == -1) { - fprintf(stderr, "=> clone failed! %m\n"); - err = 1; - goto clear_resources; - } - close(sockets[1]); - sockets[1] = 0; - close(sockets[1]); - sockets[1] = 0; - if (handle_child_uid_map(child_pid, sockets[0])) { - err = 1; - goto kill_and_finish_child; - } - - goto finish_child; -kill_and_finish_child: - if (child_pid) kill(child_pid, SIGKILL); -finish_child:; - int child_status = 0; - waitpid(child_pid, &child_status, 0); - err |= WEXITSTATUS(child_status); -clear_resources: - free_resources(&config); - free(stack); - - goto cleanup; -usage: - fprintf(stderr, "Usage: %s -u -1 -m . -c /bin/sh ~\n", argv[0]); -error: - err = 1; -cleanup: - if (sockets[0]) close(sockets[0]); - if (sockets[1]) close(sockets[1]); - return err; -} diff --git a/include/capabilities.h b/include/capabilities.h new file mode 100644 index 0000000..5e2f467 --- /dev/null +++ b/include/capabilities.h @@ -0,0 +1,6 @@ +#ifndef CAPABILITIES_H +#define CAPABILITIES_H + +int capabilities(void); + +#endif diff --git a/include/cgroup.h b/include/cgroup.h new file mode 100644 index 0000000..0d2bdd1 --- /dev/null +++ b/include/cgroup.h @@ -0,0 +1,9 @@ +#ifndef CGROUP_H +#define CGROUP_H + +#include "container.h" + +int resources(struct child_config *config); +int free_resources(struct child_config *config); + +#endif diff --git a/include/container.h b/include/container.h new file mode 100644 index 0000000..12e6cf8 --- /dev/null +++ b/include/container.h @@ -0,0 +1,41 @@ +#ifndef CONTAINER_H +#define CONTAINER_H + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define STACK_SIZE (1024 * 1024) +#define USERNS_OFFSET 10000 +#define USERNS_COUNT 2000 +#define FD_COUNT 64 + +struct child_config { + int argc; + uid_t uid; + int fd; + char *hostname; + char **argv; + char *mount_dir; +}; + +#endif diff --git a/include/container_seccomp.h b/include/container_seccomp.h new file mode 100644 index 0000000..32ba623 --- /dev/null +++ b/include/container_seccomp.h @@ -0,0 +1,6 @@ +#ifndef CONTAINER_SECCOMP_H +#define CONTAINER_SECCOMP_H + +int syscalls(void); + +#endif diff --git a/include/hostname.h b/include/hostname.h new file mode 100644 index 0000000..1805c0d --- /dev/null +++ b/include/hostname.h @@ -0,0 +1,8 @@ +#ifndef HOSTNAME_H +#define HOSTNAME_H + +#include + +int choose_hostname(char *buff, size_t len); + +#endif diff --git a/include/mount.h b/include/mount.h new file mode 100644 index 0000000..6775e92 --- /dev/null +++ b/include/mount.h @@ -0,0 +1,8 @@ +#ifndef MOUNT_H +#define MOUNT_H + +#include "container.h" + +int mounts(struct child_config *config); + +#endif diff --git a/include/userns.h b/include/userns.h new file mode 100644 index 0000000..9053f98 --- /dev/null +++ b/include/userns.h @@ -0,0 +1,9 @@ +#ifndef USERNS_H +#define USERNS_H + +#include "container.h" + +int userns(struct child_config *config); +int handle_child_uid_map(pid_t child_pid, int fd); + +#endif diff --git a/src/capabilities.c b/src/capabilities.c new file mode 100644 index 0000000..5be3ce4 --- /dev/null +++ b/src/capabilities.c @@ -0,0 +1,52 @@ +#include +#include +#include +#include +#include "capabilities.h" + +int capabilities(void) +{ + fprintf(stderr, "=> dropping capabilities..."); + int drop_caps[] = { + CAP_AUDIT_CONTROL, + CAP_AUDIT_READ, + CAP_AUDIT_WRITE, + CAP_BLOCK_SUSPEND, + CAP_DAC_READ_SEARCH, + CAP_FSETID, + CAP_IPC_LOCK, + CAP_MAC_ADMIN, + CAP_MAC_OVERRIDE, + CAP_MKNOD, + CAP_SETFCAP, + CAP_SYSLOG, + CAP_SYS_ADMIN, + CAP_SYS_BOOT, + CAP_SYS_MODULE, + CAP_SYS_NICE, + CAP_SYS_RAWIO, + CAP_SYS_RESOURCE, + CAP_SYS_TIME, + CAP_WAKE_ALARM + }; + size_t num_caps = sizeof(drop_caps) / sizeof(*drop_caps); + fprintf(stderr, "bounding..."); + for (size_t i = 0; i < num_caps; i++) { + if (prctl(PR_CAPBSET_DROP, drop_caps[i], 0, 0, 0)) { + fprintf(stderr, "prctl failed: %m\n"); + return 1; + } + } + fprintf(stderr, "inheritable..."); + cap_t caps = NULL; + if (!(caps = cap_get_proc()) + || cap_set_flag(caps, CAP_INHERITABLE, num_caps, drop_caps, CAP_CLEAR) + || cap_set_proc(caps)) { + fprintf(stderr, "failed: %m\n"); + if (caps) cap_free(caps); + return 1; + } + cap_free(caps); + fprintf(stderr, "done.\n"); + return 0; +} diff --git a/src/cgroup.c b/src/cgroup.c new file mode 100644 index 0000000..a1ca0d6 --- /dev/null +++ b/src/cgroup.c @@ -0,0 +1,142 @@ +#include "container.h" +#include "cgroup.h" + +#define MEMORY "1073741824" +#define SHARES "256" +#define PIDS "64" +#define WEIGHT "10" + +struct cgrp_control { + char control[256]; + struct cgrp_setting { + char name[256]; + char value[256]; + } **settings; +}; + +static struct cgrp_setting add_to_tasks = { + .name = "tasks", + .value = "0" +}; + +static struct cgrp_control *cgrps[] = { + & (struct cgrp_control) { + .control = "memory", + .settings = (struct cgrp_setting *[]) { + & (struct cgrp_setting) { + .name = "memory.limit_in_bytes", + .value = MEMORY + }, + & (struct cgrp_setting) { + .name = "memory.kmem.limit_in_bytes", + .value = MEMORY + }, + &add_to_tasks, + NULL + } + }, + & (struct cgrp_control) { + .control = "cpu", + .settings = (struct cgrp_setting *[]) { + & (struct cgrp_setting) { + .name = "cpu.shares", + .value = SHARES + }, + &add_to_tasks, + NULL + } + }, + & (struct cgrp_control) { + .control = "pids", + .settings = (struct cgrp_setting *[]) { + & (struct cgrp_setting) { + .name = "pids.max", + .value = PIDS + }, + &add_to_tasks, + NULL + } + }, + NULL +}; + +int resources(struct child_config *config) +{ + fprintf(stderr, "=> setting cgroups..."); + for (struct cgrp_control **cgrp = cgrps; *cgrp; cgrp++) { + char dir[PATH_MAX] = {0}; + fprintf(stderr, "%s...", (*cgrp)->control); + if (snprintf(dir, sizeof(dir), "/sys/fs/cgroup/%s/%s", + (*cgrp)->control, config->hostname) == -1) { + return -1; + } + if (mkdir(dir, S_IRUSR | S_IWUSR | S_IXUSR)) { + fprintf(stderr, "mkdir %s failed: %m\n", dir); + return -1; + } + for (struct cgrp_setting **setting = (*cgrp)->settings; *setting; setting++) { + char path[PATH_MAX] = {0}; + int fd = 0; + if (snprintf(path, sizeof(path), "%s/%s", dir, + (*setting)->name) == -1) { + fprintf(stderr, "snprintf failed: %m\n"); + return -1; + } + if ((fd = open(path, O_WRONLY)) == -1) { + fprintf(stderr, "opening %s failed: %m\n", path); + return -1; + } + if (write(fd, (*setting)->value, strlen((*setting)->value)) == -1) { + fprintf(stderr, "writing to %s failed: %m\n", path); + close(fd); + return -1; + } + close(fd); + } + } + fprintf(stderr, "done.\n"); + fprintf(stderr, "=> setting rlimit..."); + if (setrlimit(RLIMIT_NOFILE, + & (struct rlimit) { + .rlim_max = FD_COUNT, + .rlim_cur = FD_COUNT, + })) { + fprintf(stderr, "failed: %m\n"); + return 1; + } + fprintf(stderr, "done.\n"); + return 0; +} + +int free_resources(struct child_config *config) +{ + fprintf(stderr, "=> cleaning cgroups..."); + for (struct cgrp_control **cgrp = cgrps; *cgrp; cgrp++) { + char dir[PATH_MAX] = {0}; + char task[PATH_MAX] = {0}; + int task_fd = 0; + if (snprintf(dir, sizeof(dir), "/sys/fs/cgroup/%s/%s", + (*cgrp)->control, config->hostname) == -1 + || snprintf(task, sizeof(task), "/sys/fs/cgroup/%s/tasks", + (*cgrp)->control) == -1) { + fprintf(stderr, "snprintf failed: %m\n"); + return -1; + } + if ((task_fd = open(task, O_WRONLY)) == -1) { + fprintf(stderr, "opening %s failed: %m\n", task); + return -1; + } + if (write(task_fd, "0", 2) == -1) { + fprintf(stderr, "writing to %s failed: %m\n", task); + close(task_fd); + return -1; + } + close(task_fd); + if (rmdir(dir)) { + fprintf(stderr, "rmdir %s failed: %m", dir); + return -1; + } + } + fprintf(stderr, "done.\n"); + return 0; +} diff --git a/src/hostname.c b/src/hostname.c new file mode 100644 index 0000000..31a8d4a --- /dev/null +++ b/src/hostname.c @@ -0,0 +1,32 @@ +#include +#include +#include "hostname.h" + +int choose_hostname(char *buff, size_t len) +{ + static const char *suits[] = { "swords", "wands", "pentacles", "cups" }; + static const char *minor[] = { + "ace", "two", "three", "four", "five", "six", "seven", "eight", + "nine", "ten", "page", "knight", "queen", "king" + }; + static const char *major[] = { + "fool", "magician", "high-priestess", "empress", "emperor", + "hierophant", "lovers", "chariot", "strength", "hermit", + "wheel", "justice", "hanged-man", "death", "temperance", + "devil", "tower", "star", "moon", "sun", "judgment", "world" + }; + struct timespec now = {0}; + clock_gettime(CLOCK_MONOTONIC, &now); + size_t ix = now.tv_nsec % 78; + if (ix < sizeof(major) / sizeof(*major)) { + snprintf(buff, len, "%05lx-%s", now.tv_sec, major[ix]); + } else { + ix -= sizeof(major) / sizeof(*major); + snprintf(buff, len, + "%05lxc-%s-of-%s", + now.tv_sec, + minor[ix % (sizeof(minor) / sizeof(*minor))], + suits[ix / (sizeof(minor) / sizeof(*minor))]); + } + return 0; +} diff --git a/src/main.c b/src/main.c new file mode 100644 index 0000000..9017b78 --- /dev/null +++ b/src/main.c @@ -0,0 +1,149 @@ +#include "container.h" +#include "mount.h" +#include "cgroup.h" +#include "capabilities.h" +#include "container_seccomp.h" +#include "userns.h" +#include "hostname.h" + +int child(void *arg) +{ + struct child_config *config = arg; + if (sethostname(config->hostname, strlen(config->hostname)) + || mounts(config) + || userns(config) + || capabilities() + || syscalls()) { + close(config->fd); + return -1; + } + if (close(config->fd)) { + fprintf(stderr, "close failed: %m\n"); + return -1; + } + if (execve(config->argv[0], config->argv, NULL)) { + fprintf(stderr, "execve failed! %m.\n"); + return -1; + } + return 0; +} + +int main(int argc, char **argv) +{ + struct child_config config = {0}; + int err = 0; + int option = 0; + int sockets[2] = {0}; + pid_t child_pid = 0; + int last_optind = 0; + while ((option = getopt(argc, argv, "c:m:u:"))) { + switch (option) { + case 'c': + config.argc = argc - last_optind - 1; + config.argv = &argv[argc - config.argc]; + goto finish_options; + case 'm': + config.mount_dir = optarg; + break; + case 'u': + if (sscanf(optarg, "%d", &config.uid) != 1) { + fprintf(stderr, "badly-formatted uid: %s\n", optarg); + goto usage; + } + break; + default: + goto usage; + } + last_optind = optind; + } +finish_options: + if (!config.argc) goto usage; + if (!config.mount_dir) goto usage; + + fprintf(stderr, "=> validating Linux version..."); + struct utsname host = {0}; + if (uname(&host)) { + fprintf(stderr, "failed: %m\n"); + goto cleanup; + } + int major = -1; + int minor = -1; + if (sscanf(host.release, "%u.%u.", &major, &minor) != 2) { + fprintf(stderr, "weird release format: %s\n", host.release); + goto cleanup; + } + if (major < 4 || (major == 4 && minor < 7)) { + fprintf(stderr, "kernel too old: %s (need >= 4.7)\n", host.release); + goto cleanup; + } + if (strcmp("x86_64", host.machine)) { + fprintf(stderr, "expected x86_64: %s\n", host.machine); + goto cleanup; + } + fprintf(stderr, "%s on %s.\n", host.release, host.machine); + + char hostname[256] = {0}; + if (choose_hostname(hostname, sizeof(hostname))) + goto error; + config.hostname = hostname; + + if (socketpair(AF_LOCAL, SOCK_SEQPACKET, 0, sockets)) { + fprintf(stderr, "socketpair failed: %m\n"); + goto error; + } + if (fcntl(sockets[0], F_SETFD, FD_CLOEXEC)) { + fprintf(stderr, "fcntl failed: %m\n"); + goto error; + } + config.fd = sockets[1]; + + char *stack = 0; + if (!(stack = malloc(STACK_SIZE))) { + fprintf(stderr, "=> malloc failed, out of memory?\n"); + goto error; + } + if (resources(&config)) { + err = 1; + goto clear_resources; + } + int flags = CLONE_NEWNS + | CLONE_NEWCGROUP + | CLONE_NEWPID + | CLONE_NEWIPC + | CLONE_NEWNET + | CLONE_NEWUTS; + if ((child_pid = clone(child, stack + STACK_SIZE, flags | SIGCHLD, &config)) == -1) { + fprintf(stderr, "=> clone failed! %m\n"); + err = 1; + goto clear_resources; + } + close(sockets[1]); + sockets[1] = 0; + close(sockets[1]); + sockets[1] = 0; + if (handle_child_uid_map(child_pid, sockets[0])) { + err = 1; + goto kill_and_finish_child; + } + + goto finish_child; +kill_and_finish_child: + if (child_pid) kill(child_pid, SIGKILL); +finish_child:; + int child_status = 0; + waitpid(child_pid, &child_status, 0); + err |= WEXITSTATUS(child_status); +clear_resources: + free_resources(&config); + free(stack); + + goto cleanup; +usage: + fprintf(stderr, "Usage: %s -u -1 -m . -c /bin/sh ~\n", argv[0]); +error: + err = 1; +cleanup: + if (sockets[0]) close(sockets[0]); + if (sockets[1]) close(sockets[1]); + return err; +} diff --git a/src/mount.c b/src/mount.c new file mode 100644 index 0000000..d3e10e7 --- /dev/null +++ b/src/mount.c @@ -0,0 +1,89 @@ +#include "container.h" +#include "mount.h" + +static int pivot_root(const char *new_root, const char *put_old) +{ + return syscall(SYS_pivot_root, new_root, put_old); +} + +int mounts(struct child_config *config) +{ + fprintf(stderr, "=> remounting everything with MS_PRIVATE..."); + if (mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) { + fprintf(stderr, "failed! %m\n"); + return -1; + } + fprintf(stderr, "remounted.\n"); + + fprintf(stderr, "=> making a temp directory and a bind mount there..."); + char mount_dir[] = "/tmp/tmp.XXXXXX"; + if (!mkdtemp(mount_dir)) { + fprintf(stderr, "failed making a directory!\n"); + return -1; + } + + if (mount(config->mount_dir, mount_dir, NULL, MS_BIND | MS_PRIVATE, NULL)) { + fprintf(stderr, "bind mount failed!\n"); + return -1; + } + + char inner_mount_dir[] = "/tmp/tmp.XXXXXX/oldroot.XXXXXX"; + memcpy(inner_mount_dir, mount_dir, sizeof(mount_dir) - 1); + if (!mkdtemp(inner_mount_dir)) { + fprintf(stderr, "failed making the inner directory!\n"); + return -1; + } + fprintf(stderr, "done.\n"); + + fprintf(stderr, "=> pivoting root..."); + if (pivot_root(mount_dir, inner_mount_dir)) { + fprintf(stderr, "failed!\n"); + return -1; + } + fprintf(stderr, "done.\n"); + + char *old_root_dir = basename(inner_mount_dir); + char old_root[sizeof(inner_mount_dir) + 1] = { "/" }; + strcpy(&old_root[1], old_root_dir); + + fprintf(stderr, "=> unmounting %s...", old_root); + if (chdir("/")) { + fprintf(stderr, "chdir failed! %m\n"); + return -1; + } + if (umount2(old_root, MNT_DETACH)) { + fprintf(stderr, "umount failed! %m\n"); + return -1; + } + if (rmdir(old_root)) { + fprintf(stderr, "rmdir failed! %m\n"); + return -1; + } + fprintf(stderr, "done.\n"); + + fprintf(stderr, "=> mounting /proc..."); + if (mount("proc", "/proc", "proc", 0, NULL)) { + fprintf(stderr, "failed: %m\n"); + return -1; + } + fprintf(stderr, "done.\n"); + + fprintf(stderr, "=> mounting /dev..."); + if (mount("tmpfs", "/dev", "tmpfs", MS_NOSUID | MS_STRICTATIME, "mode=755,size=65536k")) { + fprintf(stderr, "failed: %m\n"); + return -1; + } + umask(0); + if (mknod("/dev/null", S_IFCHR | 0666, makedev(1, 3))) { + fprintf(stderr, "mknod null failed: %m\n"); + } + if (mknod("/dev/zero", S_IFCHR | 0666, makedev(1, 5))) { + fprintf(stderr, "mknod zero failed: %m\n"); + } + if (mknod("/dev/urandom", S_IFCHR | 0666, makedev(1, 9))) { + fprintf(stderr, "mknod urandom failed: %m\n"); + } + fprintf(stderr, "done.\n"); + + return 0; +} diff --git a/src/seccomp.c b/src/seccomp.c new file mode 100644 index 0000000..9015cb2 --- /dev/null +++ b/src/seccomp.c @@ -0,0 +1,50 @@ +#include "container.h" +#include "container_seccomp.h" +#include +#include + +#define SCMP_FAIL SCMP_ACT_ERRNO(EPERM) + +int syscalls(void) +{ + scmp_filter_ctx ctx = NULL; + fprintf(stderr, "=> filtering syscalls..."); + if (!(ctx = seccomp_init(SCMP_ACT_ALLOW)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(chmod), 1, + SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISUID, S_ISUID)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(chmod), 1, + SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISGID, S_ISGID)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmod), 1, + SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISUID, S_ISUID)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmod), 1, + SCMP_A1(SCMP_CMP_MASKED_EQ, S_ISGID, S_ISGID)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmodat), 1, + SCMP_A2(SCMP_CMP_MASKED_EQ, S_ISUID, S_ISUID)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(fchmodat), 1, + SCMP_A2(SCMP_CMP_MASKED_EQ, S_ISGID, S_ISGID)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(unshare), 1, + SCMP_A0(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(clone), 1, + SCMP_A0(SCMP_CMP_MASKED_EQ, CLONE_NEWUSER, CLONE_NEWUSER)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(ioctl), 1, + SCMP_A1(SCMP_CMP_MASKED_EQ, TIOCSTI, TIOCSTI)) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(keyctl), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(add_key), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(request_key), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(ptrace), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(mbind), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(migrate_pages), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(move_pages), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(set_mempolicy), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(userfaultfd), 0) + || seccomp_rule_add(ctx, SCMP_FAIL, SCMP_SYS(perf_event_open), 0) + || seccomp_attr_set(ctx, SCMP_FLTATR_CTL_NNP, 0) + || seccomp_load(ctx)) { + if (ctx) seccomp_release(ctx); + fprintf(stderr, "failed: %m\n"); + return 1; + } + seccomp_release(ctx); + fprintf(stderr, "done.\n"); + return 0; +} diff --git a/src/userns.c b/src/userns.c new file mode 100644 index 0000000..b5b2e68 --- /dev/null +++ b/src/userns.c @@ -0,0 +1,68 @@ +#include "container.h" +#include "userns.h" + +int handle_child_uid_map(pid_t child_pid, int fd) +{ + int uid_map = 0; + int has_userns = -1; + if (read(fd, &has_userns, sizeof(has_userns)) != sizeof(has_userns)) { + fprintf(stderr, "couldn't read from child!\n"); + return -1; + } + if (has_userns) { + char path[PATH_MAX] = {0}; + for (char **file = (char *[]) { "uid_map", "gid_map", 0 }; *file; file++) { + if (snprintf(path, sizeof(path), "/proc/%d/%s", child_pid, *file) + > sizeof(path)) { + fprintf(stderr, "snprintf too big? %m\n"); + return -1; + } + fprintf(stderr, "writing %s...", path); + if ((uid_map = open(path, O_WRONLY)) == -1) { + fprintf(stderr, "open failed: %m\n"); + return -1; + } + if (dprintf(uid_map, "0 %d %d\n", USERNS_OFFSET, USERNS_COUNT) == -1) { + fprintf(stderr, "dprintf failed: %m\n"); + close(uid_map); + return -1; + } + close(uid_map); + } + } + if (write(fd, & (int) { 0 }, sizeof(int)) != sizeof(int)) { + fprintf(stderr, "couldn't write: %m\n"); + return -1; + } + return 0; +} + +int userns(struct child_config *config) +{ + fprintf(stderr, "=> trying a user namespace..."); + int has_userns = !unshare(CLONE_NEWUSER); + if (write(config->fd, &has_userns, sizeof(has_userns)) != sizeof(has_userns)) { + fprintf(stderr, "couldn't write: %m\n"); + return -1; + } + int result = 0; + if (read(config->fd, &result, sizeof(result)) != sizeof(result)) { + fprintf(stderr, "couldn't read: %m\n"); + return -1; + } + if (result) return -1; + if (has_userns) { + fprintf(stderr, "done.\n"); + } else { + fprintf(stderr, "unsupported? continuing.\n"); + } + fprintf(stderr, "=> switching to uid %d / gid %d...", config->uid, config->uid); + if (setgroups(1, & (gid_t) { config->uid }) || + setresgid(config->uid, config->uid, config->uid) || + setresuid(config->uid, config->uid, config->uid)) { + fprintf(stderr, "%m\n"); + return -1; + } + fprintf(stderr, "done.\n"); + return 0; +} diff --git a/test-container.sh b/test-container.sh new file mode 100644 index 0000000..fc3f663 --- /dev/null +++ b/test-container.sh @@ -0,0 +1,52 @@ +#!/bin/sh + +echo "=========================================" +echo " Container Environment Test" +echo "=========================================" + +echo "" +echo "[1] Process Info" +echo " PID: $$" +echo " User: $(id)" +echo " Home: $HOME" +echo " Shell: $SHELL" + +echo "" +echo "[2] Hostname" +echo " $(hostname)" + +echo "" +echo "[3] Process List" +ps aux 2>/dev/null || echo " ps not available, using /proc" +echo " PIDs in /proc:" +ls -d /proc/[0-9]* 2>/dev/null | head -20 + +echo "" +echo "[4] Network Interfaces" +ip addr 2>/dev/null || ifconfig 2>/dev/null || echo " no network tools, reading /proc/net" +cat /proc/net/dev 2>/dev/null | head -5 + +echo "" +echo "[5] Mount Points" +mount 2>/dev/null || cat /proc/mounts 2>/dev/null | head -20 + +echo "" +echo "[6] Filesystem Root" +ls / + +echo "" +echo "[7] Memory Limits" +cat /proc/meminfo 2>/dev/null | head -3 + +echo "" +echo "[8] Capabilities" +cat /proc/self/status 2>/dev/null | grep -i cap + +echo "" +echo "[9] Seccomp Mode" +cat /proc/self/status 2>/dev/null | grep Seccomp + +echo "" +echo "=========================================" +echo " Test Complete" +echo "========================================="