diff --git a/Makefile b/Makefile index 7532c8a..4fa3ef2 100644 --- a/Makefile +++ b/Makefile @@ -22,6 +22,7 @@ CC ?= gcc CFLAGS += -O2 -c -std=gnu11 \ -D_GNU_SOURCE \ -fstack-protector-all -Wformat -Wformat=2 -Wformat-security -fPIE \ + -Wno-format-nonliteral \ -Wall -Wextra -Werror LDFLAGS += -Wl,-z,now -Wl,-z,relro -pie -Wl,-z,noexecstack diff --git a/cmdline.c b/cmdline.c index 264032f..c22cd48 100644 --- a/cmdline.c +++ b/cmdline.c @@ -101,13 +101,12 @@ void cmdlineLogParams(struct nsjconf_t *nsjconf) LOG_I ("Jail parameters: hostname:'%s', chroot:'%s', process:'%s', bind:[%s]:%d, " - "max_conns_per_ip:%u, uid:(ns:%u, global:%u), gid:(ns:%u, global:%u), time_limit:%ld, personality:%#lx, daemonize:%s, " + "max_conns_per_ip:%u, time_limit:%ld, personality:%#lx, daemonize:%s, " "clone_newnet:%s, clone_newuser:%s, clone_newns:%s, clone_newpid:%s, " "clone_newipc:%s, clonew_newuts:%s, clone_newcgroup:%s, keep_caps:%s, " "tmpfs_size:%zu, disable_no_new_privs:%s, pivot_root_only:%s", nsjconf->hostname, nsjconf->chroot, nsjconf->argv[0], nsjconf->bindhost, nsjconf->port, - nsjconf->max_conns_per_ip, nsjconf->inside_uid, nsjconf->outside_uid, - nsjconf->inside_gid, nsjconf->outside_gid, nsjconf->tlimit, nsjconf->personality, + nsjconf->max_conns_per_ip, nsjconf->tlimit, nsjconf->personality, logYesNo(nsjconf->daemonize), logYesNo(nsjconf->clone_newnet), logYesNo(nsjconf->clone_newuser), logYesNo(nsjconf->clone_newns), logYesNo(nsjconf->clone_newpid), logYesNo(nsjconf->clone_newipc), @@ -122,15 +121,25 @@ void cmdlineLogParams(struct nsjconf_t *nsjconf) p->src, p->dst, p->fs_type, p->flags, p->options); } } + { + struct idmap_t *p; + TAILQ_FOREACH(p, &nsjconf->uids, pointers) { + LOG_I("Uid map: inside_uid:%d outside_uid:%d", p->inside_id, p->outside_id); + } + TAILQ_FOREACH(p, &nsjconf->gids, pointers) { + LOG_I("Gid map: inside_gid:%d outside_gid:%d", p->inside_id, p->outside_id); + } + } + { struct mapping_t *p; TAILQ_FOREACH(p, &nsjconf->uid_mappings, pointers) { - LOG_I("Uid mapping: inside_uid:'%s' outside_uid:'%s' count:'%s'", + LOG_I("Newuid mapping: inside_uid:'%s' outside_uid:'%s' count:'%s'", p->inside_id, p->outside_id, p->count); } TAILQ_FOREACH(p, &nsjconf->gid_mappings, pointers) { - LOG_I("Gid mapping: inside_uid:'%s' outside_uid:'%s' count:'%s'", + LOG_I("Newgid mapping: inside_uid:'%s' outside_uid:'%s' count:'%s'", p->inside_id, p->outside_id, p->count); } } @@ -204,30 +213,38 @@ static bool cmdlineParseUid(struct nsjconf_t *nsjconf, char *str) } char *second = cmdlineSplitStrByColon(str); + pid_t inside_uid; + pid_t outside_uid; struct passwd *pw = getpwnam(str); if (pw != NULL) { - nsjconf->inside_uid = pw->pw_uid; + inside_uid = pw->pw_uid; } else if (cmdlineIsANumber(str)) { - nsjconf->inside_uid = (uid_t) strtoull(str, NULL, 0); + inside_uid = (uid_t) strtoull(str, NULL, 0); } else { LOG_E("No such user '%s'", str); return false; } if (str == second) { - return true; + outside_uid = inside_uid; + } else { + pw = getpwnam(second); + if (pw != NULL) { + outside_uid = pw->pw_uid; + } else if (cmdlineIsANumber(second)) { + outside_uid = (uid_t) strtoull(second, NULL, 0); + } else { + LOG_E("No such user '%s'", second); + return false; + } } - pw = getpwnam(second); - if (pw != NULL) { - nsjconf->outside_uid = pw->pw_uid; - } else if (cmdlineIsANumber(second)) { - nsjconf->outside_uid = (uid_t) strtoull(second, NULL, 0); - } else { - LOG_E("No such user '%s'", second); - return false; - } + struct idmap_t *p = utilMalloc(sizeof(struct idmap_t)); + p->inside_id = inside_uid; + p->outside_id = outside_uid; + TAILQ_INSERT_TAIL(&nsjconf->uids, p, pointers); + return true; } @@ -238,30 +255,39 @@ static bool cmdlineParseGid(struct nsjconf_t *nsjconf, char *str) } char *second = cmdlineSplitStrByColon(str); + gid_t inside_gid; + gid_t outside_gid; struct group *gr = getgrnam(str); if (gr != NULL) { - nsjconf->inside_gid = gr->gr_gid; + inside_gid = gr->gr_gid; } else if (cmdlineIsANumber(str)) { - nsjconf->inside_gid = (gid_t) strtoull(str, NULL, 0); + inside_gid = (gid_t) strtoull(str, NULL, 0); } else { LOG_E("No such group '%s'", str); return false; } if (str == second) { - return true; + outside_gid = inside_gid; + } else { + + gr = getgrnam(second); + if (gr != NULL) { + outside_gid = gr->gr_gid; + } else if (cmdlineIsANumber(second)) { + outside_gid = (gid_t) strtoull(second, NULL, 0); + } else { + LOG_E("No such group '%s'", second); + return false; + } } - gr = getgrnam(second); - if (gr != NULL) { - nsjconf->outside_gid = gr->gr_gid; - } else if (cmdlineIsANumber(second)) { - nsjconf->outside_gid = (gid_t) strtoull(second, NULL, 0); - } else { - LOG_E("No such group '%s'", second); - return false; - } + struct idmap_t *p = utilMalloc(sizeof(struct idmap_t)); + p->inside_id = inside_gid; + p->outside_id = outside_gid; + TAILQ_INSERT_TAIL(&nsjconf->gids, p, pointers); + return true; } @@ -300,10 +326,6 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) .is_root_rw = false, .is_silent = false, .skip_setsid = false, - .inside_uid = getuid(), - .inside_gid = getgid(), - .outside_uid = getuid(), - .outside_gid = getgid(), .max_conns_per_ip = 0, .tmpfs_size = 4 * (1024 * 1024), .mount_proc = true, @@ -320,6 +342,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) }; /* *INDENT-OFF* */ + TAILQ_INIT(&nsjconf->uids); + TAILQ_INIT(&nsjconf->gids); TAILQ_INIT(&nsjconf->envs); TAILQ_INIT(&nsjconf->pids); TAILQ_INIT(&nsjconf->mountpts); @@ -327,8 +351,6 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) TAILQ_INIT(&nsjconf->uid_mappings); TAILQ_INIT(&nsjconf->gid_mappings); - char *user = NULL; - char *group = NULL; const char *logfile = NULL; static char cmdlineTmpfsSz[PATH_MAX] = "size=4194304"; @@ -353,8 +375,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) "\tr: Immediately launch a single process on the console, keep doing it forever [MODE_STANDALONE_RERUN]"}, {{"chroot", required_argument, NULL, 'c'}, "Directory containing / of the jail (default: none)"}, {{"rw", no_argument, NULL, 0x601}, "Mount / as RW (default: RO)"}, - {{"user", required_argument, NULL, 'u'}, "Username/uid of processess inside the jail (default: your current uid). You can also use inside_ns_uid:outside_ns_uid convention here"}, - {{"group", required_argument, NULL, 'g'}, "Groupname/gid of processess inside the jail (default: your current gid). You can also use inside_ns_gid:global_ns_gid convention here"}, + {{"user", required_argument, NULL, 'u'}, "Username/uid of processess inside the jail (default: your current uid). You can also use inside_ns_uid:outside_ns_uid convention here. Can be specified multiple times"}, + {{"group", required_argument, NULL, 'g'}, "Groupname/gid of processess inside the jail (default: your current gid). You can also use inside_ns_gid:global_ns_gid convention here. Can be specified multiple times"}, {{"hostname", required_argument, NULL, 'H'}, "UTS name (hostname) of the jail (default: 'NSJAIL')"}, {{"cwd", required_argument, NULL, 'D'}, "Directory in the namespace the process will run (default: '/')"}, {{"port", required_argument, NULL, 'p'}, "TCP port to bind to (enables MODE_LISTEN_TCP) (default: 0)"}, @@ -445,10 +467,14 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) nsjconf->max_conns_per_ip = strtoul(optarg, NULL, 0); break; case 'u': - user = optarg; + if (cmdlineParseUid(nsjconf, optarg) == false) { + LOG_F("cmdlineParseUid('%s')", optarg); + } break; case 'g': - group = optarg; + if (cmdlineParseGid(nsjconf, optarg) == false) { + LOG_F("cmdlineParseGid('%s')", optarg); + } break; case 'l': logfile = optarg; @@ -711,6 +737,18 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) TAILQ_INSERT_HEAD(&nsjconf->mountpts, p, pointers); } + if (TAILQ_EMPTY(&nsjconf->uids)) { + struct idmap_t *p = utilMalloc(sizeof(struct idmap_t)); + p->inside_id = getuid(); + p->outside_id = getuid(); + TAILQ_INSERT_HEAD(&nsjconf->uids, p, pointers); + } + if (TAILQ_EMPTY(&nsjconf->gids)) { + struct idmap_t *p = utilMalloc(sizeof(struct idmap_t)); + p->inside_id = getgid(); + p->outside_id = getgid(); + TAILQ_INSERT_HEAD(&nsjconf->gids, p, pointers); + } #if !defined(USE_KAFEL) if (nsjconf->kafel_file != NULL || nsjconf->kafel_string != NULL) { LOG_F("Kafel policy specified but the kafel/ is not compiled in"); @@ -721,13 +759,6 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) return false; } - if (cmdlineParseUid(nsjconf, user) == false) { - return false; - } - if (cmdlineParseGid(nsjconf, group) == false) { - return false; - } - nsjconf->argv = &argv[optind]; if (nsjconf->argv[0] == NULL) { LOG_E("No command provided"); diff --git a/common.h b/common.h index 4fc363c..f443052 100644 --- a/common.h +++ b/common.h @@ -31,6 +31,7 @@ #include #define ARRAYSIZE(array) (sizeof(array) / sizeof(*array)) +#define UNUSED __attribute__((unused)) #if 0 /* Works, but needs -fblocks and libBlocksRuntime with clang */ /* Go-style defer implementation */ @@ -79,6 +80,12 @@ struct mapping_t { TAILQ_ENTRY(mapping_t) pointers; }; +struct idmap_t { + pid_t inside_id; + pid_t outside_id; + TAILQ_ENTRY(idmap_t) pointers; +}; + struct fds_t { int fd; TAILQ_ENTRY(fds_t) pointers; @@ -129,10 +136,6 @@ struct nsjconf_t { bool is_root_rw; bool is_silent; bool skip_setsid; - uid_t outside_uid; - gid_t outside_gid; - uid_t inside_uid; - gid_t inside_gid; unsigned int max_conns_per_ip; size_t tmpfs_size; bool mount_proc; @@ -146,6 +149,8 @@ struct nsjconf_t { size_t cgroup_mem_max; FILE *kafel_file; char *kafel_string; + TAILQ_HEAD(uidlist, idmap_t) uids; + TAILQ_HEAD(gidlist, idmap_t) gids; TAILQ_HEAD(envlist, charptr_t) envs; TAILQ_HEAD(pidslist, pids_t) pids; TAILQ_HEAD(mountptslist, mounts_t) mountpts; diff --git a/user.c b/user.c index 4a9ead0..93f83c8 100644 --- a/user.c +++ b/user.c @@ -59,11 +59,16 @@ static bool userSetGroups(pid_t pid) static bool userUidMapSelf(struct nsjconf_t *nsjconf, pid_t pid) { char fname[PATH_MAX]; - char map[128]; - snprintf(fname, sizeof(fname), "/proc/%d/uid_map", pid); - snprintf(map, sizeof(map), "%lu %lu 1", (unsigned long)nsjconf->inside_uid, - (unsigned long)nsjconf->outside_uid); + + char map[4096] = {[0] = '\0' }; + + struct idmap_t *p; + TAILQ_FOREACH(p, &nsjconf->uids, pointers) { + utilSSnPrintf(map, sizeof(map), "%lu %lu 1\n", (unsigned long)p->inside_id, + (unsigned long)p->outside_id); + } + LOG_D("Writing '%s' to '%s'", map, fname); if (utilWriteBufToFile(fname, map, strlen(map), O_WRONLY) == false) { LOG_E("utilWriteBufToFile('%s', '%s') failed", fname, map); @@ -76,36 +81,34 @@ static bool userUidMapSelf(struct nsjconf_t *nsjconf, pid_t pid) static bool userGidMapSelf(struct nsjconf_t *nsjconf, pid_t pid) { char fname[PATH_MAX]; - char map[128]; - snprintf(fname, sizeof(fname), "/proc/%d/gid_map", pid); - snprintf(map, sizeof(map), "%lu %lu 1", (unsigned long)nsjconf->inside_gid, - (unsigned long)nsjconf->outside_gid); + + char map[4096] = {[0] = '\0' }; + + struct idmap_t *p; + TAILQ_FOREACH(p, &nsjconf->gids, pointers) { + utilSSnPrintf(map, sizeof(map), "%lu %lu 1\n", (unsigned long)p->inside_id, + (unsigned long)p->outside_id); + } + LOG_D("Writing '%s' to '%s'", map, fname); if (utilWriteBufToFile(fname, map, strlen(map), O_WRONLY) == false) { LOG_E("utilWriteBufToFile('%s', '%s') failed", fname, map); return false; } + return true; } /* Use /usr/bin/newgidmap for writing the gid map */ -static bool userGidMapExternal(struct nsjconf_t *nsjconf, pid_t pid) +static bool userGidMapExternal(struct nsjconf_t *nsjconf, pid_t pid UNUSED) { - char pid_str[16]; - char ins_gid_str[16]; - char out_gid_str[16]; - - snprintf(pid_str, sizeof(pid_str), "%lu", (unsigned long)pid); - snprintf(ins_gid_str, sizeof(ins_gid_str), "%lu", (unsigned long)nsjconf->inside_gid); - snprintf(out_gid_str, sizeof(out_gid_str), "%lu", (unsigned long)nsjconf->outside_gid); - - const char *argv[1024] = { "/usr/bin/newgidmap", pid_str, ins_gid_str, out_gid_str, "1" }; - size_t argv_idx = 5; + const char *argv[1024] = { "/usr/bin/newgidmap" }; + size_t argv_idx = 1; struct mapping_t *p; TAILQ_FOREACH(p, &nsjconf->gid_mappings, pointers) { - if ((argv_idx + 4) >= ARRAYSIZE(argv)) { + if (argv_idx >= ARRAYSIZE(argv)) { LOG_W("Number of arguments to '/usr/bin/newgidmap' too big"); return false; } @@ -125,22 +128,14 @@ static bool userGidMapExternal(struct nsjconf_t *nsjconf, pid_t pid) } /* Use /usr/bin/newuidmap for writing the uid map */ -static bool userUidMapExternal(struct nsjconf_t *nsjconf, pid_t pid) +static bool userUidMapExternal(struct nsjconf_t *nsjconf, pid_t pid UNUSED) { - char pid_str[16]; - char ins_uid_str[16]; - char out_uid_str[16]; - - snprintf(pid_str, sizeof(pid_str), "%lu", (unsigned long)pid); - snprintf(ins_uid_str, sizeof(ins_uid_str), "%lu", (unsigned long)nsjconf->inside_uid); - snprintf(out_uid_str, sizeof(out_uid_str), "%lu", (unsigned long)nsjconf->outside_uid); - - const char *argv[1024] = { "/usr/bin/newuidmap", pid_str, ins_uid_str, out_uid_str, "1" }; - size_t argv_idx = 5; + const char *argv[1024] = { "/usr/bin/newuidmap" }; + size_t argv_idx = 1; struct mapping_t *p; TAILQ_FOREACH(p, &nsjconf->uid_mappings, pointers) { - if ((argv_idx + 4) >= ARRAYSIZE(argv)) { + if (argv_idx >= ARRAYSIZE(argv)) { LOG_W("Number of arguments to '/usr/bin/newuidmap' too big"); return false; } @@ -201,18 +196,22 @@ bool userInitNsFromChild(struct nsjconf_t * nsjconf) if (setgroups(0, group_list) == -1) { PLOG_D("setgroups(NULL) failed"); } - LOG_D("setresgid(%d, %d, %d)", nsjconf->inside_gid, nsjconf->inside_gid, - nsjconf->inside_gid); - if (syscall(__NR_setresgid, nsjconf->inside_gid, nsjconf->inside_gid, nsjconf->inside_gid) + LOG_D("setresgid(%d, %d, %d)", TAILQ_FIRST(&nsjconf->gids)->inside_id, + TAILQ_FIRST(&nsjconf->gids)->inside_id, TAILQ_FIRST(&nsjconf->gids)->inside_id); + if (syscall + (__NR_setresgid, TAILQ_FIRST(&nsjconf->gids)->inside_id, + TAILQ_FIRST(&nsjconf->gids)->inside_id, TAILQ_FIRST(&nsjconf->gids)->inside_id) == -1) { - PLOG_E("setresgid(%u)", nsjconf->inside_gid); + PLOG_E("setresgid(%u)", TAILQ_FIRST(&nsjconf->gids)->inside_id); return false; } - LOG_D("setresuid(%d, %d, %d)", nsjconf->inside_uid, nsjconf->inside_uid, - nsjconf->inside_uid); - if (syscall(__NR_setresuid, nsjconf->inside_uid, nsjconf->inside_uid, nsjconf->inside_uid) + LOG_D("setresuid(%d, %d, %d)", TAILQ_FIRST(&nsjconf->uids)->inside_id, + TAILQ_FIRST(&nsjconf->uids)->inside_id, TAILQ_FIRST(&nsjconf->uids)->inside_id); + if (syscall + (__NR_setresuid, TAILQ_FIRST(&nsjconf->uids)->inside_id, + TAILQ_FIRST(&nsjconf->uids)->inside_id, TAILQ_FIRST(&nsjconf->uids)->inside_id) == -1) { - PLOG_E("setresuid(%u)", nsjconf->inside_uid); + PLOG_E("setresuid(%u)", TAILQ_FIRST(&nsjconf->uids)->inside_id); return false; } diff --git a/util.c b/util.c index afb0083..8d4b0e7 100644 --- a/util.c +++ b/util.c @@ -23,6 +23,7 @@ #include #include +#include #include #include #include @@ -157,3 +158,18 @@ bool utilCreateDirRecursively(const char *dir) curr = next + 1; } } + +int utilSSnPrintf(char *str, size_t size, const char *format, ...) +{ + char buf1[size]; + char buf2[size]; + + snprintf(buf1, sizeof(buf1), "%s", str); + + va_list args; + va_start(args, format); + vsnprintf(buf2, size, format, args); + va_end(args); + + return snprintf(str, size, "%s%s", buf1, buf2); +} diff --git a/util.h b/util.h index b3d6438..77ca612 100644 --- a/util.h +++ b/util.h @@ -33,5 +33,6 @@ ssize_t utilReadFromFile(const char *fname, void *buf, size_t len); ssize_t utilWriteToFd(int fd, const void *buf, size_t len); bool utilWriteBufToFile(const char *filename, const void *buf, size_t len, int open_flags); bool utilCreateDirRecursively(const char *dir); +int utilSSnPrintf(char *str, size_t size, const char *format, ...); #endif /* NS_UTIL_H */