summaryrefslogtreecommitdiffstats
path: root/src/nsresourced/test-userns-restrict.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/nsresourced/test-userns-restrict.c')
-rw-r--r--src/nsresourced/test-userns-restrict.c182
1 files changed, 182 insertions, 0 deletions
diff --git a/src/nsresourced/test-userns-restrict.c b/src/nsresourced/test-userns-restrict.c
new file mode 100644
index 0000000..f509321
--- /dev/null
+++ b/src/nsresourced/test-userns-restrict.c
@@ -0,0 +1,182 @@
+/* SPDX-License-Identifier: LGPL-2.1-or-later */
+
+#include <sys/eventfd.h>
+
+#include "fd-util.h"
+#include "main-func.h"
+#include "missing_mount.h"
+#include "missing_syscall.h"
+#include "namespace-util.h"
+#include "process-util.h"
+#include "rm-rf.h"
+#include "tmpfile-util.h"
+#include "userns-restrict.h"
+
+static int make_tmpfs_fsmount(void) {
+ _cleanup_close_ int fsfd = -EBADF, mntfd = -EBADF;
+
+ fsfd = fsopen("tmpfs", FSOPEN_CLOEXEC);
+ assert_se(fsfd >= 0);
+ assert_se(fsconfig(fsfd, FSCONFIG_CMD_CREATE, NULL, NULL, 0) >= 0);
+
+ mntfd = fsmount(fsfd, FSMOUNT_CLOEXEC, 0);
+ assert_se(mntfd >= 0);
+
+ return TAKE_FD(mntfd);
+}
+
+static void test_works_reg(int parent_fd, const char *fname) {
+ _cleanup_close_ int fd = -EBADF;
+
+ fd = openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666);
+ assert_se(fd >= 0);
+}
+
+static void test_fails_reg(int parent_fd, const char *fname) {
+ errno = 0;
+ assert_se(openat(parent_fd, fname, O_RDWR|O_CREAT|O_CLOEXEC, 0666) < 0);
+ assert_se(errno == EPERM);
+}
+
+static void test_works_dir(int parent_fd, const char *fname) {
+ assert_se(mkdirat(parent_fd, fname, 0666) >= 0);
+}
+
+static void test_fails_dir(int parent_fd, const char *fname) {
+ errno = 0;
+ assert_se(mkdirat(parent_fd, fname, 0666) < 0);
+ assert_se(errno == EPERM);
+}
+
+static int run(int argc, char *argv[]) {
+ _cleanup_(userns_restrict_bpf_freep) struct userns_restrict_bpf *obj = NULL;
+ _cleanup_close_ int userns_fd = -EBADF, host_fd1 = -EBADF, host_tmpfs = -EBADF, afd = -EBADF, bfd = -EBADF;
+ _cleanup_(rm_rf_physical_and_freep) char *t = NULL;
+ _cleanup_(sigkill_waitp) pid_t pid = 0;
+ int r;
+
+ log_set_max_level(LOG_DEBUG);
+ log_setup();
+
+ r = userns_restrict_install(/* pin= */ false, &obj);
+ if (ERRNO_IS_NOT_SUPPORTED(r)) {
+ log_notice("Skipping test, LSM-BPF logic not supported.");
+ return EXIT_TEST_SKIP;
+ }
+ if (ERRNO_IS_PRIVILEGE(r)) {
+ log_notice("Skipping test, lacking privileges.");
+ return EXIT_TEST_SKIP;
+ }
+ if (r < 0)
+ return r;
+
+ assert_se(mkdtemp_malloc(NULL, &t) >= 0);
+
+ host_fd1 = open(t, O_DIRECTORY|O_CLOEXEC);
+ assert_se(host_fd1 >= 0);
+
+ host_tmpfs = make_tmpfs_fsmount();
+ assert_se(host_tmpfs >= 0);
+
+ userns_fd = userns_acquire("0 0 1", "0 0 1");
+ if (userns_fd < 0)
+ return log_error_errno(userns_fd, "Failed to make user namespace: %m");
+
+ r = userns_restrict_put_by_fd(
+ obj,
+ userns_fd,
+ /* replace= */ true,
+ /* mount_fds= */ NULL,
+ /* n_mount_fds= */ 0);
+ if (r < 0)
+ return log_error_errno(r, "Failed to restrict user namespace: %m");
+
+ afd = eventfd(0, EFD_CLOEXEC);
+ bfd = eventfd(0, EFD_CLOEXEC);
+
+ assert_se(afd >= 0 && bfd >= 0);
+
+ r = safe_fork("(test)", FORK_DEATHSIG_SIGKILL, &pid);
+ assert_se(r >= 0);
+ if (r == 0) {
+ _cleanup_close_ int private_tmpfs = -EBADF;
+
+ assert_se(setns(userns_fd, CLONE_NEWUSER) >= 0);
+ assert_se(unshare(CLONE_NEWNS) >= 0);
+
+ /* Allocate tmpfs locally */
+ private_tmpfs = make_tmpfs_fsmount();
+
+ /* These two host mounts should be inaccessible */
+ test_fails_reg(host_fd1, "test");
+ test_fails_reg(host_tmpfs, "xxx");
+ test_fails_dir(host_fd1, "test2");
+ test_fails_dir(host_tmpfs, "xxx2");
+
+ /* But this mount created locally should be fine */
+ test_works_reg(private_tmpfs, "yyy");
+ test_works_dir(private_tmpfs, "yyy2");
+
+ /* Let's sync with the parent, so that it allowlists more stuff for us */
+ assert_se(eventfd_write(afd, 1) >= 0);
+ uint64_t x;
+ assert_se(eventfd_read(bfd, &x) >= 0);
+
+ /* And now we should also have access to the host tmpfs */
+ test_works_reg(host_tmpfs, "zzz");
+ test_works_reg(private_tmpfs, "aaa");
+ test_works_dir(host_tmpfs, "zzz2");
+ test_works_dir(private_tmpfs, "aaa2");
+
+ /* But this one should still fail */
+ test_fails_reg(host_fd1, "bbb");
+ test_fails_dir(host_fd1, "bbb2");
+
+ /* Sync again, to get more stuff allowlisted */
+ assert_se(eventfd_write(afd, 1) >= 0);
+ assert_se(eventfd_read(bfd, &x) >= 0);
+
+ /* Everything should now be allowed */
+ test_works_reg(host_tmpfs, "ccc");
+ test_works_reg(host_fd1, "ddd");
+ test_works_reg(private_tmpfs, "eee");
+ test_works_dir(host_tmpfs, "ccc2");
+ test_works_reg(host_fd1, "ddd2");
+ test_works_dir(private_tmpfs, "eee2");
+
+ _exit(EXIT_SUCCESS);
+ }
+
+ uint64_t x;
+ assert_se(eventfd_read(afd, &x) >= 0);
+
+ r = userns_restrict_put_by_fd(
+ obj,
+ userns_fd,
+ /* replace= */ false,
+ &host_tmpfs,
+ 1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to loosen user namespace: %m");
+
+ assert_se(eventfd_write(bfd, 1) >= 0);
+
+ assert_se(eventfd_read(afd, &x) >= 0);
+
+ r = userns_restrict_put_by_fd(
+ obj,
+ userns_fd,
+ /* replace= */ false,
+ &host_fd1,
+ 1);
+ if (r < 0)
+ return log_error_errno(r, "Failed to loosen user namespace: %m");
+
+ assert_se(eventfd_write(bfd, 1) >= 0);
+
+ assert_se(wait_for_terminate_and_check("(test)", pid, WAIT_LOG) >= 0);
+
+ return 0;
+}
+
+DEFINE_MAIN_FUNCTION(run);