1*d64ba78bSChristian Brauner // SPDX-License-Identifier: GPL-2.0 2*d64ba78bSChristian Brauner /* 3*d64ba78bSChristian Brauner * Copyright (C) 2026 Christian Brauner <brauner@kernel.org> 4*d64ba78bSChristian Brauner * 5*d64ba78bSChristian Brauner * Test that completing a filesystem context from another user namespace 6*d64ba78bSChristian Brauner * doesn't warn. 7*d64ba78bSChristian Brauner * 8*d64ba78bSChristian Brauner * fsopen() records the caller's user namespace in fc->user_ns and hands 9*d64ba78bSChristian Brauner * back an ordinary file descriptor. The task that issues 10*d64ba78bSChristian Brauner * FSCONFIG_CMD_CREATE need not be the one that created the context: the fd 11*d64ba78bSChristian Brauner * is inherited across fork() and exec() and it can be passed over a unix 12*d64ba78bSChristian Brauner * socket. vfs_cmd_create() authorizes the create with mount_capable(), 13*d64ba78bSChristian Brauner * which for FS_USERNS_MOUNT checks ns_capable(fc->user_ns, CAP_SYS_ADMIN), 14*d64ba78bSChristian Brauner * and that succeeds for a task holding CAP_SYS_ADMIN in an ancestor of 15*d64ba78bSChristian Brauner * fc->user_ns. 16*d64ba78bSChristian Brauner * 17*d64ba78bSChristian Brauner * binfmt_misc and overlayfs used to WARN_ON() that mismatch, which let an 18*d64ba78bSChristian Brauner * unprivileged user taint the kernel, flood the log and panic a kernel 19*d64ba78bSChristian Brauner * booted with panic_on_warn. The mount must still be refused, but it must 20*d64ba78bSChristian Brauner * not warn. 21*d64ba78bSChristian Brauner */ 22*d64ba78bSChristian Brauner #define _GNU_SOURCE 23*d64ba78bSChristian Brauner 24*d64ba78bSChristian Brauner #include <errno.h> 25*d64ba78bSChristian Brauner #include <sched.h> 26*d64ba78bSChristian Brauner #include <stdio.h> 27*d64ba78bSChristian Brauner #include <stdlib.h> 28*d64ba78bSChristian Brauner #include <string.h> 29*d64ba78bSChristian Brauner #include <sys/socket.h> 30*d64ba78bSChristian Brauner #include <sys/wait.h> 31*d64ba78bSChristian Brauner #include <unistd.h> 32*d64ba78bSChristian Brauner 33*d64ba78bSChristian Brauner #include "../wrappers.h" 34*d64ba78bSChristian Brauner #include "../utils.h" 35*d64ba78bSChristian Brauner #include "../../kselftest_harness.h" 36*d64ba78bSChristian Brauner 37*d64ba78bSChristian Brauner #ifndef FSCONFIG_CMD_CREATE 38*d64ba78bSChristian Brauner #define FSCONFIG_CMD_CREATE 6 39*d64ba78bSChristian Brauner #endif 40*d64ba78bSChristian Brauner 41*d64ba78bSChristian Brauner /* TAINT_WARN, i.e. bit 9 of /proc/sys/kernel/tainted. */ 42*d64ba78bSChristian Brauner #define TAINT_WARN_BIT 9 43*d64ba78bSChristian Brauner 44*d64ba78bSChristian Brauner static bool taint_warn_set(void) 45*d64ba78bSChristian Brauner { 46*d64ba78bSChristian Brauner unsigned long taint = 0; 47*d64ba78bSChristian Brauner FILE *f; 48*d64ba78bSChristian Brauner 49*d64ba78bSChristian Brauner f = fopen("/proc/sys/kernel/tainted", "r"); 50*d64ba78bSChristian Brauner if (!f) 51*d64ba78bSChristian Brauner return false; 52*d64ba78bSChristian Brauner if (fscanf(f, "%lu", &taint) != 1) 53*d64ba78bSChristian Brauner taint = 0; 54*d64ba78bSChristian Brauner fclose(f); 55*d64ba78bSChristian Brauner 56*d64ba78bSChristian Brauner return taint & (1UL << TAINT_WARN_BIT); 57*d64ba78bSChristian Brauner } 58*d64ba78bSChristian Brauner 59*d64ba78bSChristian Brauner static int send_fd(int sock, int fd) 60*d64ba78bSChristian Brauner { 61*d64ba78bSChristian Brauner char cmsgbuf[CMSG_SPACE(sizeof(int))] = {}; 62*d64ba78bSChristian Brauner char b[1] = { 'x' }; 63*d64ba78bSChristian Brauner struct iovec iov = { .iov_base = b, .iov_len = sizeof(b) }; 64*d64ba78bSChristian Brauner struct msghdr msg = { 65*d64ba78bSChristian Brauner .msg_iov = &iov, 66*d64ba78bSChristian Brauner .msg_iovlen = 1, 67*d64ba78bSChristian Brauner .msg_control = cmsgbuf, 68*d64ba78bSChristian Brauner .msg_controllen = sizeof(cmsgbuf), 69*d64ba78bSChristian Brauner }; 70*d64ba78bSChristian Brauner struct cmsghdr *cmsg; 71*d64ba78bSChristian Brauner 72*d64ba78bSChristian Brauner cmsg = CMSG_FIRSTHDR(&msg); 73*d64ba78bSChristian Brauner cmsg->cmsg_level = SOL_SOCKET; 74*d64ba78bSChristian Brauner cmsg->cmsg_type = SCM_RIGHTS; 75*d64ba78bSChristian Brauner cmsg->cmsg_len = CMSG_LEN(sizeof(int)); 76*d64ba78bSChristian Brauner memcpy(CMSG_DATA(cmsg), &fd, sizeof(int)); 77*d64ba78bSChristian Brauner 78*d64ba78bSChristian Brauner return sendmsg(sock, &msg, 0) < 0 ? -1 : 0; 79*d64ba78bSChristian Brauner } 80*d64ba78bSChristian Brauner 81*d64ba78bSChristian Brauner static int recv_fd(int sock) 82*d64ba78bSChristian Brauner { 83*d64ba78bSChristian Brauner char cmsgbuf[CMSG_SPACE(sizeof(int))] = {}; 84*d64ba78bSChristian Brauner char b[1]; 85*d64ba78bSChristian Brauner struct iovec iov = { .iov_base = b, .iov_len = sizeof(b) }; 86*d64ba78bSChristian Brauner struct msghdr msg = { 87*d64ba78bSChristian Brauner .msg_iov = &iov, 88*d64ba78bSChristian Brauner .msg_iovlen = 1, 89*d64ba78bSChristian Brauner .msg_control = cmsgbuf, 90*d64ba78bSChristian Brauner .msg_controllen = sizeof(cmsgbuf), 91*d64ba78bSChristian Brauner }; 92*d64ba78bSChristian Brauner struct cmsghdr *cmsg; 93*d64ba78bSChristian Brauner int fd = -1; 94*d64ba78bSChristian Brauner 95*d64ba78bSChristian Brauner if (recvmsg(sock, &msg, 0) <= 0) 96*d64ba78bSChristian Brauner return -1; 97*d64ba78bSChristian Brauner 98*d64ba78bSChristian Brauner cmsg = CMSG_FIRSTHDR(&msg); 99*d64ba78bSChristian Brauner if (!cmsg || cmsg->cmsg_type != SCM_RIGHTS) 100*d64ba78bSChristian Brauner return -1; 101*d64ba78bSChristian Brauner memcpy(&fd, CMSG_DATA(cmsg), sizeof(int)); 102*d64ba78bSChristian Brauner 103*d64ba78bSChristian Brauner return fd; 104*d64ba78bSChristian Brauner } 105*d64ba78bSChristian Brauner 106*d64ba78bSChristian Brauner /* 107*d64ba78bSChristian Brauner * Create a context for @fsname in a child and complete it here. With @nest 108*d64ba78bSChristian Brauner * the child first creates its own user namespace, so that the context is 109*d64ba78bSChristian Brauner * created in a descendant of the namespace completing it. The child needs a 110*d64ba78bSChristian Brauner * mount namespace of its own as well: fsopen() gates on may_mount(), which 111*d64ba78bSChristian Brauner * asks for CAP_SYS_ADMIN in the user namespace owning the caller's mount 112*d64ba78bSChristian Brauner * namespace. 113*d64ba78bSChristian Brauner * 114*d64ba78bSChristian Brauner * Returns the result of FSCONFIG_CMD_CREATE with errno set, or -ENODATA if 115*d64ba78bSChristian Brauner * the child could not create the context at all. 116*d64ba78bSChristian Brauner */ 117*d64ba78bSChristian Brauner static int create_from_child(const char *fsname, bool nest) 118*d64ba78bSChristian Brauner { 119*d64ba78bSChristian Brauner int sock[2], fd, ret, status; 120*d64ba78bSChristian Brauner pid_t pid; 121*d64ba78bSChristian Brauner 122*d64ba78bSChristian Brauner if (socketpair(AF_UNIX, SOCK_STREAM, 0, sock)) 123*d64ba78bSChristian Brauner return -ENODATA; 124*d64ba78bSChristian Brauner 125*d64ba78bSChristian Brauner pid = fork(); 126*d64ba78bSChristian Brauner if (pid < 0) { 127*d64ba78bSChristian Brauner close(sock[0]); 128*d64ba78bSChristian Brauner close(sock[1]); 129*d64ba78bSChristian Brauner return -ENODATA; 130*d64ba78bSChristian Brauner } 131*d64ba78bSChristian Brauner 132*d64ba78bSChristian Brauner if (pid == 0) { 133*d64ba78bSChristian Brauner close(sock[0]); 134*d64ba78bSChristian Brauner 135*d64ba78bSChristian Brauner if (nest && unshare(CLONE_NEWUSER | CLONE_NEWNS)) 136*d64ba78bSChristian Brauner _exit(1); 137*d64ba78bSChristian Brauner 138*d64ba78bSChristian Brauner fd = sys_fsopen(fsname, 0); 139*d64ba78bSChristian Brauner if (fd < 0) 140*d64ba78bSChristian Brauner _exit(1); 141*d64ba78bSChristian Brauner if (send_fd(sock[1], fd)) 142*d64ba78bSChristian Brauner _exit(1); 143*d64ba78bSChristian Brauner _exit(0); 144*d64ba78bSChristian Brauner } 145*d64ba78bSChristian Brauner 146*d64ba78bSChristian Brauner close(sock[1]); 147*d64ba78bSChristian Brauner fd = recv_fd(sock[0]); 148*d64ba78bSChristian Brauner close(sock[0]); 149*d64ba78bSChristian Brauner wait_for_pid(pid); 150*d64ba78bSChristian Brauner waitpid(pid, &status, WNOHANG); 151*d64ba78bSChristian Brauner 152*d64ba78bSChristian Brauner if (fd < 0) 153*d64ba78bSChristian Brauner return -ENODATA; 154*d64ba78bSChristian Brauner 155*d64ba78bSChristian Brauner errno = 0; 156*d64ba78bSChristian Brauner ret = sys_fsconfig(fd, FSCONFIG_CMD_CREATE, NULL, NULL, 0); 157*d64ba78bSChristian Brauner status = errno; 158*d64ba78bSChristian Brauner close(fd); 159*d64ba78bSChristian Brauner errno = status; 160*d64ba78bSChristian Brauner 161*d64ba78bSChristian Brauner return ret; 162*d64ba78bSChristian Brauner } 163*d64ba78bSChristian Brauner 164*d64ba78bSChristian Brauner FIXTURE(fscontext_ns) { 165*d64ba78bSChristian Brauner bool warn_before; 166*d64ba78bSChristian Brauner }; 167*d64ba78bSChristian Brauner 168*d64ba78bSChristian Brauner FIXTURE_SETUP(fscontext_ns) 169*d64ba78bSChristian Brauner { 170*d64ba78bSChristian Brauner self->warn_before = taint_warn_set(); 171*d64ba78bSChristian Brauner 172*d64ba78bSChristian Brauner if (setup_userns() != 0) 173*d64ba78bSChristian Brauner SKIP(return, "setup_userns failed"); 174*d64ba78bSChristian Brauner } 175*d64ba78bSChristian Brauner 176*d64ba78bSChristian Brauner FIXTURE_TEARDOWN(fscontext_ns) 177*d64ba78bSChristian Brauner { 178*d64ba78bSChristian Brauner } 179*d64ba78bSChristian Brauner 180*d64ba78bSChristian Brauner /* 181*d64ba78bSChristian Brauner * The condition the kernel used to WARN about. It has to be refused, and it 182*d64ba78bSChristian Brauner * has to be refused quietly: an unprivileged task reaches this. 183*d64ba78bSChristian Brauner */ 184*d64ba78bSChristian Brauner FIXTURE_VARIANT(fscontext_ns) { 185*d64ba78bSChristian Brauner const char *fsname; 186*d64ba78bSChristian Brauner int expected_errno; 187*d64ba78bSChristian Brauner }; 188*d64ba78bSChristian Brauner 189*d64ba78bSChristian Brauner FIXTURE_VARIANT_ADD(fscontext_ns, binfmt_misc) { 190*d64ba78bSChristian Brauner .fsname = "binfmt_misc", 191*d64ba78bSChristian Brauner .expected_errno = EINVAL, 192*d64ba78bSChristian Brauner }; 193*d64ba78bSChristian Brauner 194*d64ba78bSChristian Brauner FIXTURE_VARIANT_ADD(fscontext_ns, overlay) { 195*d64ba78bSChristian Brauner .fsname = "overlay", 196*d64ba78bSChristian Brauner .expected_errno = EIO, 197*d64ba78bSChristian Brauner }; 198*d64ba78bSChristian Brauner 199*d64ba78bSChristian Brauner TEST_F(fscontext_ns, create_from_descendant_userns) 200*d64ba78bSChristian Brauner { 201*d64ba78bSChristian Brauner int ret; 202*d64ba78bSChristian Brauner 203*d64ba78bSChristian Brauner ret = create_from_child(variant->fsname, true); 204*d64ba78bSChristian Brauner if (ret == -ENODATA) 205*d64ba78bSChristian Brauner SKIP(return, "%s unavailable", variant->fsname); 206*d64ba78bSChristian Brauner 207*d64ba78bSChristian Brauner ASSERT_EQ(-1, ret); 208*d64ba78bSChristian Brauner ASSERT_EQ(variant->expected_errno, errno); 209*d64ba78bSChristian Brauner 210*d64ba78bSChristian Brauner /* 211*d64ba78bSChristian Brauner * Only meaningful if nothing had warned before us. Note that an 212*d64ba78bSChristian Brauner * unrelated warning racing this test would look like a failure. 213*d64ba78bSChristian Brauner */ 214*d64ba78bSChristian Brauner if (self->warn_before) 215*d64ba78bSChristian Brauner TH_LOG("TAINT_WARN already set, not checking for a new warning"); 216*d64ba78bSChristian Brauner else 217*d64ba78bSChristian Brauner ASSERT_FALSE(taint_warn_set()); 218*d64ba78bSChristian Brauner } 219*d64ba78bSChristian Brauner 220*d64ba78bSChristian Brauner /* 221*d64ba78bSChristian Brauner * The same handover within one user namespace is a supported thing to do and 222*d64ba78bSChristian Brauner * has to keep working. binfmt_misc takes no options, so the create succeeds 223*d64ba78bSChristian Brauner * outright and this also shows the test really drives the create path. 224*d64ba78bSChristian Brauner */ 225*d64ba78bSChristian Brauner TEST(create_from_same_userns) 226*d64ba78bSChristian Brauner { 227*d64ba78bSChristian Brauner int ret; 228*d64ba78bSChristian Brauner 229*d64ba78bSChristian Brauner if (setup_userns() != 0) 230*d64ba78bSChristian Brauner SKIP(return, "setup_userns failed"); 231*d64ba78bSChristian Brauner 232*d64ba78bSChristian Brauner ret = create_from_child("binfmt_misc", false); 233*d64ba78bSChristian Brauner if (ret == -ENODATA) 234*d64ba78bSChristian Brauner SKIP(return, "binfmt_misc unavailable"); 235*d64ba78bSChristian Brauner 236*d64ba78bSChristian Brauner ASSERT_EQ(0, ret); 237*d64ba78bSChristian Brauner } 238*d64ba78bSChristian Brauner 239*d64ba78bSChristian Brauner TEST_HARNESS_MAIN 240