From 4c3cea0bf30321a8ec0bd8f3e9387924eb11867e Mon Sep 17 00:00:00 2001 From: ikeago <339441827+ikeago@users.noreply.github.com> Date: Mon, 12 Oct 2026 11:11:48 -0700 Subject: [PATCH] pid: fail closed when dummy init setup fails --- Makefile | 11 +++++-- pid.cc | 41 +++++++++++++++++++++++--- tests/pid_init_failure_inject.cc | 49 ++++++++++++++++++++++++++++++++ 3 files changed, 95 insertions(+), 6 deletions(-) create mode 100644 tests/pid_init_failure_inject.cc diff --git a/Makefile b/Makefile index 68c308bf..ad015331 100644 --- a/Makefile +++ b/Makefile @@ -46,6 +46,7 @@ endif BIN = nsjail LIBS = kafel/libkafel.a TEST_BINS = tests/nstun_buffer_budget_test tests/nstun_policy_test tests/nstun_ip_test tests/nstun_udp_peer_test +PID_INIT_INJECT = tests/pid_init_failure_inject.so # If PASTA_BIN_PATH is not provided in env, dynamically search for it if EMBED_PASTA is requested # or fallback to it naturally. @@ -89,6 +90,9 @@ endif %.o: %.cc | $(SRCS_PB_H) $(CXX) $(CXXFLAGS) $< -o $@ +$(PID_INIT_INJECT): tests/pid_init_failure_inject.cc + $(CXX) -shared -fPIC -std=c++20 -Wall -Wextra -Werror $< -o $@ -ldl + # Protobuf Generation # We only define the recipe for the .cc file to prevent race conditions. $(SRCS_PB_CXX): $(SRCS_PROTO) @@ -110,7 +114,7 @@ kafel/libkafel.a: kafel_init # Utilities clean: - $(RM) core Makefile.bak $(OBJS) $(SRCS_PB_CXX) $(SRCS_PB_H) $(SRCS_PB_O) $(BIN) $(TEST_BINS) + $(RM) core Makefile.bak $(OBJS) $(SRCS_PB_CXX) $(SRCS_PB_H) $(SRCS_PB_O) $(BIN) $(TEST_BINS) $(PID_INIT_INJECT) ifneq ("$(wildcard kafel/Makefile)","") +$(MAKE) -C kafel clean endif @@ -160,7 +164,7 @@ test-cmdline: $(BIN) $(call run_test, ./nsjail --time_limit 0x10 --cgroup_mem_swap_max=-1 --user 1000:1000:1 --help > /dev/null, 0) .PHONY: test -test: $(BIN) $(TEST_BINS) test-cmdline +test: $(BIN) $(TEST_BINS) $(PID_INIT_INJECT) test-cmdline $(call run_test, ./tests/nstun_buffer_budget_test, 0) $(call run_test, ./tests/nstun_policy_test, 0) $(call run_test, ./tests/nstun_ip_test, 0) @@ -174,6 +178,9 @@ ifeq ($(UID),0) $(call run_test, setpriv --reuid 1000 --regid 1000 --clear-groups -- ./nsjail -q -Mo --user 65534 --group 65534 --disable_clone_newnet --disable_clone_newcgroup --disable_clone_newns --disable_clone_newpid --disable_clone_newipc --disable_clone_newuts --disable_proc -- /bin/true, 0) $(call run_test, setsid --fork --wait ./nsjail -q -Me --disable_clone_newuser --disable_clone_newnet --disable_clone_newcgroup --disable_clone_newns --disable_clone_newpid --disable_clone_newipc --disable_clone_newuts --disable_proc --chroot / -- /bin/true, 0) $(call run_test, strace -f -qq -e inject=setsid:error=EPERM ./nsjail -q -Me --disable_clone_newuser --disable_clone_newnet --disable_clone_newcgroup --disable_clone_newns --disable_clone_newpid --disable_clone_newipc --disable_clone_newuts --disable_proc --chroot / -- /bin/true, 255) + $(call run_test, NSJAIL_TEST_FAIL_INIT=pdeathsig LD_PRELOAD=$(CURDIR)/$(PID_INIT_INJECT) ./nsjail -q -Me --disable_clone_newuser --disable_clone_newnet --disable_clone_newcgroup --disable_clone_newns --disable_clone_newipc --disable_clone_newuts --disable_proc --chroot / -- /bin/true, 255) + $(call run_test, NSJAIL_TEST_FAIL_INIT=dumpable LD_PRELOAD=$(CURDIR)/$(PID_INIT_INJECT) ./nsjail -q -Me --disable_clone_newuser --disable_clone_newnet --disable_clone_newcgroup --disable_clone_newns --disable_clone_newipc --disable_clone_newuts --disable_proc --chroot / -- /bin/true, 255) + $(call run_test, NSJAIL_TEST_FAIL_INIT=sigaction LD_PRELOAD=$(CURDIR)/$(PID_INIT_INJECT) ./nsjail -q -Me --disable_clone_newuser --disable_clone_newnet --disable_clone_newcgroup --disable_clone_newns --disable_clone_newipc --disable_clone_newuts --disable_proc --chroot / -- /bin/true, 255) endif $(call run_test, ./nsjail -q -Mo --chroot / --user 99999 --group 99999 -- /bin/true < /tmp, 255) $(call run_test, ./nsjail -q -Mo --chroot / --user 99999 --group 99999 --pass_fd 0 -- /bin/true < /tmp, 0) diff --git a/pid.cc b/pid.cc index 9381499b..8923ea62 100644 --- a/pid.cc +++ b/pid.cc @@ -21,15 +21,18 @@ #include "pid.h" +#include #include #include #include #include #include +#include #include #include "logs.h" #include "subproc.h" +#include "util.h" namespace pid { @@ -42,6 +45,11 @@ bool initNs(nsj_t* nsj) { } LOG_D("Creating a dummy 'init' process"); + int status_pipe[2]; + if (pipe2(status_pipe, O_CLOEXEC) == -1) { + PLOG_E("Couldn't create status pipe for dummy init process"); + return false; + } /* * If -Me is used then we need to create permanent init inside PID ns, otherwise only the @@ -50,22 +58,40 @@ bool initNs(nsj_t* nsj) { */ pid_t pid = subproc::cloneProc(CLONE_FS, 0); if (pid == -1) { + close(status_pipe[0]); + close(status_pipe[1]); PLOG_E("Couldn't create a dummy init process"); return false; } if (pid > 0) { + close(status_pipe[1]); + char status; + const bool initialized = + util::readFromFd(status_pipe[0], &status, sizeof(status)) == sizeof(status) && + status == 'I'; + close(status_pipe[0]); + if (!initialized) { + LOG_E("Dummy init process failed to initialize"); + kill(pid, SIGKILL); + while (waitpid(pid, nullptr, __WALL) == -1 && errno == EINTR) { + } + return false; + } LOG_D("Created a dummy 'init' process with PID=%d", pid); return true; } + close(status_pipe[0]); if (prctl(PR_SET_PDEATHSIG, SIGKILL, 0UL, 0UL, 0UL) == -1) { - PLOG_W("(prctl(PR_SET_PDEATHSIG, SIGKILL) failed"); + PLOG_E("prctl(PR_SET_PDEATHSIG, SIGKILL) failed"); + _exit(EXIT_FAILURE); } if (prctl(PR_SET_NAME, "ns-init", 0UL, 0UL, 0UL) == -1) { - PLOG_W("(prctl(PR_SET_NAME, 'init') failed"); + PLOG_W("prctl(PR_SET_NAME, 'init') failed"); } if (prctl(PR_SET_DUMPABLE, 0UL, 0UL, 0UL, 0UL) == -1) { - PLOG_W("(prctl(PR_SET_DUMPABLE, 0) failed"); + PLOG_E("prctl(PR_SET_DUMPABLE, 0) failed"); + _exit(EXIT_FAILURE); } /* Act sort-a like a init by reaping zombie processes */ @@ -76,8 +102,15 @@ bool initNs(nsj_t* nsj) { sigemptyset(&sa.sa_mask); if (sigaction(SIGCHLD, &sa, NULL) == -1) { - PLOG_W("Couldn't set sighandler for SIGCHLD"); + PLOG_E("Couldn't set sighandler for SIGCHLD"); + _exit(EXIT_FAILURE); + } + + const char initialized = 'I'; + if (!util::writeToFd(status_pipe[1], &initialized, sizeof(initialized))) { + _exit(EXIT_FAILURE); } + close(status_pipe[1]); for (;;) { pause(); diff --git a/tests/pid_init_failure_inject.cc b/tests/pid_init_failure_inject.cc new file mode 100644 index 00000000..4ba15647 --- /dev/null +++ b/tests/pid_init_failure_inject.cc @@ -0,0 +1,49 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { + +pid_t original_pid; + +__attribute__((constructor)) void rememberOriginalPid() { + original_pid = getpid(); +} + +} // namespace + +extern "C" int prctl( + int option, unsigned long arg2, unsigned long arg3, unsigned long arg4, unsigned long arg5) { + const char* failure = getenv("NSJAIL_TEST_FAIL_INIT"); + if (getpid() != original_pid && failure != nullptr && + ((option == PR_SET_PDEATHSIG && strcmp(failure, "pdeathsig") == 0) || + (option == PR_SET_DUMPABLE && strcmp(failure, "dumpable") == 0))) { + errno = EPERM; + return -1; + } + return syscall(SYS_prctl, option, arg2, arg3, arg4, arg5); +} + +extern "C" int sigaction(int signum, const struct sigaction* act, struct sigaction* oldact) { + const char* failure = getenv("NSJAIL_TEST_FAIL_INIT"); + if (getpid() != original_pid && signum == SIGCHLD && failure != nullptr && + strcmp(failure, "sigaction") == 0) { + errno = EPERM; + return -1; + } + + using Sigaction = int (*)(int, const struct sigaction*, struct sigaction*); + static const auto real_sigaction = + reinterpret_cast(dlsym(RTLD_NEXT, "sigaction")); + if (real_sigaction == nullptr) { + errno = ENOSYS; + return -1; + } + return real_sigaction(signum, act, oldact); +}