| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * An 'F' entry keeps its interpreter open for as long as the entry exists, | |
| * and the entry only goes away when the binfmt_misc superblock is destroyed. | |
| * An interpreter that lives on a mount which in turn keeps that superblock | |
| * alive therefore pins the instance that owns it, and nothing can break the | |
| * cycle. Check the two ways userspace could arrange for that: an interpreter | |
| * on the binfmt_misc instance itself, and one on a filesystem stacked on it. | |
| * | |
| * Runs unprivileged in a user namespace; binfmt_misc is FS_USERNS_MOUNT. | |
| */ | |
| #define _GNU_SOURCE | |
| #include <fcntl.h> | |
| #include <limits.h> | |
| #include <sched.h> | |
| #include <sys/mount.h> | |
| #include <sys/stat.h> | |
| #include "../filesystems/utils.h" | |
| #include "kselftest_harness.h" | |
| #define MNT "/tmp/binfmt_selfpin" | |
| #define BACKING "/tmp/binfmt_selfpin_back" | |
| #define LOWER BACKING "/lower" | |
| #define MERGED "/tmp/binfmt_selfpin_merged" | |
| #define MAGIC "\\xde\\xad" | |
| #define RULE(interp) ":selfpin:M::" MAGIC "::" interp ":F" | |
| /* Not on the instance, and unlike /bin/true it always exists. */ | |
| #define INTERP "/proc/self/exe" | |
| #define OPTS_MAX (3 * PATH_MAX + 64) | |
| static int ensure_dir(const char *path) | |
| { | |
| if (mkdir(path, 0755) && errno != EEXIST) | |
| return -1; | |
| return 0; | |
| } | |
| /* Write @rule to this instance's register file, preserving write(2)'s errno. */ | |
| static int register_at(struct __test_metadata *_metadata, const char *rule) | |
| { | |
| int fd, saved; | |
| ssize_t n; | |
| fd = open(MNT "/register", O_WRONLY); | |
| ASSERT_GE(fd, 0); | |
| n = write(fd, rule, strlen(rule)); | |
| saved = errno; | |
| close(fd); | |
| errno = saved; | |
| return n < 0 ? -1 : 0; | |
| } | |
| /* | |
| * Mount an overlay over @lower using a private upper/work pair, so the two | |
| * mounts this test performs cannot interfere with each other and neither | |
| * overlaps the lower layer. | |
| */ | |
| static int mount_overlay(const char *lower, int nr) | |
| { | |
| char opts[OPTS_MAX], upper[PATH_MAX], work[PATH_MAX]; | |
| snprintf(upper, sizeof(upper), "%s/upper%d", BACKING, nr); | |
| snprintf(work, sizeof(work), "%s/work%d", BACKING, nr); | |
| if (mkdir(upper, 0755) || mkdir(work, 0755)) | |
| return -1; | |
| snprintf(opts, sizeof(opts), "lowerdir=%s,upperdir=%s,workdir=%s", | |
| lower, upper, work); | |
| return mount("ovl", MERGED, "overlay", 0, opts); | |
| } | |
| FIXTURE(selfpin) { | |
| }; | |
| FIXTURE_SETUP(selfpin) | |
| { | |
| /* setup_userns() exits rather than returns if this is not there. */ | |
| if (access("/proc/self/ns/user", F_OK)) | |
| SKIP(return, "kernel without user namespaces"); | |
| ASSERT_EQ(setup_userns(), 0); | |
| ASSERT_EQ(ensure_dir(MNT), 0); | |
| if (mount("binfmt_misc", MNT, "binfmt_misc", 0, NULL)) { | |
| int saved = errno; | |
| /* Teardown doesn't run when setup skips, so clean up here. */ | |
| rmdir(MNT); | |
| SKIP(return, "no binfmt_misc: %s", strerror(saved)); | |
| } | |
| } | |
| FIXTURE_TEARDOWN(selfpin) | |
| { | |
| /* The namespaces go with the process; just don't litter /tmp. */ | |
| umount2(MERGED, MNT_DETACH); | |
| umount2(BACKING, MNT_DETACH); | |
| umount2(MNT, MNT_DETACH); | |
| rmdir(MERGED); | |
| rmdir(BACKING); | |
| rmdir(MNT); | |
| } | |
| /* | |
| * The instance's own files are regular files the mounter owns, so they can be | |
| * made executable. Opening one for exec still has to fail, otherwise the entry | |
| * pins the very superblock it lives in. | |
| */ | |
| TEST_F(selfpin, interpreter_on_the_instance) | |
| { | |
| ASSERT_EQ(chmod(MNT "/status", 0755), 0); | |
| ASSERT_NE(register_at(_metadata, RULE(MNT "/status")), 0); | |
| EXPECT_EQ(errno, EACCES); | |
| } | |
| /* Same for an entry file rather than one of the control files. */ | |
| TEST_F(selfpin, interpreter_on_an_entry) | |
| { | |
| ASSERT_EQ(register_at(_metadata, ":victim:M::" MAGIC "::" INTERP ":"), 0); | |
| ASSERT_EQ(chmod(MNT "/victim", 0755), 0); | |
| ASSERT_NE(register_at(_metadata, RULE(MNT "/victim")), 0); | |
| EXPECT_EQ(errno, EACCES); | |
| } | |
| /* | |
| * A stacking filesystem holds a private clone of each layer for its whole | |
| * lifetime, so an instance used as a layer can be pinned by an interpreter | |
| * that does not live on it at all. Refuse to be a layer. | |
| */ | |
| TEST_F(selfpin, refuses_to_be_stacked_on) | |
| { | |
| ASSERT_EQ(ensure_dir(BACKING), 0); | |
| ASSERT_EQ(mount("tmpfs", BACKING, "tmpfs", 0, NULL), 0); | |
| ASSERT_EQ(mkdir(LOWER, 0755), 0); | |
| ASSERT_EQ(ensure_dir(MERGED), 0); | |
| /* Nothing to prove unless overlayfs works here at all. */ | |
| if (mount_overlay(LOWER, 1)) { | |
| if (errno == ENODEV || errno == EPERM) | |
| SKIP(return, "no unprivileged overlayfs"); | |
| SKIP(return, "overlayfs unusable here: %s", strerror(errno)); | |
| } | |
| ASSERT_EQ(umount(MERGED), 0); | |
| EXPECT_NE(mount_overlay(MNT, 2), 0); | |
| } | |
| /* An ordinary interpreter still registers with 'F'. */ | |
| TEST_F(selfpin, ordinary_interpreter_still_works) | |
| { | |
| EXPECT_EQ(register_at(_metadata, RULE(INTERP)), 0); | |
| } | |
| TEST_HARNESS_MAIN |