| // SPDX-License-Identifier: GPL-2.0 |
| /* |
| * A pre-opened interpreter - what 'F' gives a static entry and what a 'B' |
| * entry binds - keeps a file open for as long as the entry lives, so it pins |
| * the mount it came from. It costs no file descriptor, and binfmt_misc is |
| * FS_USERNS_MOUNT, so an unprivileged user namespace can create them without |
| * bound. Check that UCOUNT_BINFMT_MISC_INTERPRETERS bounds it, that an entry |
| * that pre-opens nothing is not charged, that removing an entry gives the |
| * charge back, and that nesting a user namespace does not evade it. |
| * |
| * Runs unprivileged in a user namespace. |
| */ |
| #define _GNU_SOURCE |
| #include <errno.h> |
| #include <fcntl.h> |
| #include <limits.h> |
| #include <stdio.h> |
| #include <string.h> |
| #include <sys/mount.h> |
| #include <sys/stat.h> |
| #include <unistd.h> |
| |
| #include "../filesystems/utils.h" |
| #include "kselftest_harness.h" |
| |
| #define MNT "/tmp/binfmt_interplimit" |
| #define NESTED_MNT "/tmp/binfmt_interplimit_nested" |
| #define LIMIT_SYSCTL "/proc/sys/user/max_binfmt_misc_interpreters" |
| |
| #define MAGIC "\\xde\\xad" |
| /* Not on the instance, and unlike /bin/true it always exists. */ |
| #define INTERP "/proc/self/exe" |
| |
| /* Small enough to fill by hand, big enough that a refund is visible. */ |
| #define LIMIT 4 |
| |
| /* What UCOUNT_ENTRY() lets a namespace raise its own limit to. */ |
| #define LIMIT_MAX "2147483647" |
| |
| static int ensure_dir(const char *path) |
| { |
| if (mkdir(path, 0755) && errno != EEXIST) |
| return -1; |
| return 0; |
| } |
| |
| /* Write @val to @path, preserving write(2)'s errno for the caller. */ |
| static int write_keep_errno(const char *path, const char *val) |
| { |
| int fd, saved; |
| ssize_t n; |
| |
| fd = open(path, O_WRONLY | O_CLOEXEC); |
| if (fd < 0) |
| return -1; |
| n = write(fd, val, strlen(val)); |
| saved = errno; |
| close(fd); |
| errno = saved; |
| return n < 0 ? -1 : 0; |
| } |
| |
| static int set_limit(const char *val) |
| { |
| return write_keep_errno(LIMIT_SYSCTL, val); |
| } |
| |
| static int register_at(const char *mnt, const char *rule) |
| { |
| char path[PATH_MAX]; |
| |
| snprintf(path, sizeof(path), "%s/register", mnt); |
| return write_keep_errno(path, rule); |
| } |
| |
| /* An 'F' entry: one interpreter pre-opened at registration, one charge. */ |
| static int register_fixed(const char *mnt, const char *name) |
| { |
| char rule[PATH_MAX]; |
| |
| snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":F", name); |
| return register_at(mnt, rule); |
| } |
| |
| /* The same entry without 'F': the interpreter is opened per exec instead. */ |
| static int register_plain(const char *mnt, const char *name) |
| { |
| char rule[PATH_MAX]; |
| |
| snprintf(rule, sizeof(rule), ":%s:M::" MAGIC "::" INTERP ":", name); |
| return register_at(mnt, rule); |
| } |
| |
| static int remove_entry(const char *mnt, const char *name) |
| { |
| char path[PATH_MAX]; |
| |
| snprintf(path, sizeof(path), "%s/%s", mnt, name); |
| return write_keep_errno(path, "-1\n"); |
| } |
| |
| static bool entry_exists(const char *mnt, const char *name) |
| { |
| char path[PATH_MAX]; |
| |
| snprintf(path, sizeof(path), "%s/%s", mnt, name); |
| return access(path, F_OK) == 0; |
| } |
| |
| /* Register @n 'F' entries, each with a name of its own. */ |
| static int fill_budget(const char *mnt, unsigned int n) |
| { |
| char name[32]; |
| unsigned int i; |
| |
| for (i = 0; i < n; i++) { |
| snprintf(name, sizeof(name), "fixed%u", i); |
| if (register_fixed(mnt, name)) |
| return -1; |
| } |
| return 0; |
| } |
| |
| FIXTURE(interp_limit) { |
| }; |
| |
| FIXTURE_SETUP(interp_limit) |
| { |
| /* 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); |
| |
| /* CAP_SYS_RESOURCE in this namespace is what makes it writable. */ |
| if (set_limit(LIMIT_MAX)) { |
| if (errno == ENOENT) |
| SKIP(return, "kernel without " LIMIT_SYSCTL); |
| SKIP(return, "cannot set the limit: %s", strerror(errno)); |
| } |
| |
| 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(interp_limit) |
| { |
| /* The namespaces go with the process; just don't litter /tmp. */ |
| umount2(NESTED_MNT, MNT_DETACH); |
| umount2(MNT, MNT_DETACH); |
| rmdir(NESTED_MNT); |
| rmdir(MNT); |
| } |
| |
| /* Every pre-opened interpreter is charged, and the budget is a hard stop. */ |
| TEST_F(interp_limit, fixed_interpreters_are_charged) |
| { |
| char buf[32]; |
| |
| snprintf(buf, sizeof(buf), "%u", LIMIT); |
| ASSERT_EQ(set_limit(buf), 0); |
| |
| ASSERT_EQ(fill_budget(MNT, LIMIT), 0); |
| |
| EXPECT_NE(register_fixed(MNT, "over"), 0); |
| EXPECT_EQ(errno, ENOSPC); |
| |
| /* A refused registration leaves nothing behind. */ |
| EXPECT_FALSE(entry_exists(MNT, "over")); |
| } |
| |
| /* An entry that pre-opens nothing pins nothing, so it is not charged. */ |
| TEST_F(interp_limit, plain_entries_are_not_charged) |
| { |
| ASSERT_EQ(set_limit("0"), 0); |
| |
| EXPECT_EQ(register_plain(MNT, "plain"), 0); |
| EXPECT_TRUE(entry_exists(MNT, "plain")); |
| |
| /* ... while the same entry with 'F' has nothing to spend. */ |
| EXPECT_NE(register_fixed(MNT, "fixed"), 0); |
| EXPECT_EQ(errno, ENOSPC); |
| } |
| |
| /* Removing an entry closes its interpreters and gives the charge back. */ |
| TEST_F(interp_limit, removal_refunds_the_charge) |
| { |
| char buf[32]; |
| |
| snprintf(buf, sizeof(buf), "%u", LIMIT); |
| ASSERT_EQ(set_limit(buf), 0); |
| |
| ASSERT_EQ(fill_budget(MNT, LIMIT), 0); |
| ASSERT_NE(register_fixed(MNT, "over"), 0); |
| |
| ASSERT_EQ(remove_entry(MNT, "fixed0"), 0); |
| EXPECT_EQ(register_fixed(MNT, "over"), 0); |
| } |
| |
| /* |
| * The charge walks the ancestors, so a namespace cannot buy itself budget by |
| * nesting: it may raise only its own limit, and the parent it was created |
| * from is charged for every binding made below it. |
| */ |
| TEST_F(interp_limit, nesting_does_not_evade_it) |
| { |
| char buf[32]; |
| |
| snprintf(buf, sizeof(buf), "%u", LIMIT); |
| ASSERT_EQ(set_limit(buf), 0); |
| ASSERT_EQ(fill_budget(MNT, LIMIT), 0); |
| |
| ASSERT_EQ(setup_userns(), 0); |
| ASSERT_EQ(set_limit(LIMIT_MAX), 0); |
| |
| ASSERT_EQ(ensure_dir(NESTED_MNT), 0); |
| ASSERT_EQ(mount("binfmt_misc", NESTED_MNT, "binfmt_misc", 0, NULL), 0); |
| |
| /* A fresh instance with an unlimited budget of its own, and yet: */ |
| EXPECT_NE(register_fixed(NESTED_MNT, "nested"), 0); |
| EXPECT_EQ(errno, ENOSPC); |
| |
| /* The nested instance works for anything that pins no file. */ |
| EXPECT_EQ(register_plain(NESTED_MNT, "nested_plain"), 0); |
| } |
| |
| TEST_HARNESS_MAIN |