From mboxrd@z Thu Jan 1 00:00:00 1970 Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.remote-ip=100.103.45.18 ARC-Seal: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; cv=none; t=1792838326; b=PzKyPkqeum4/JHp1S8HjArowg6MTeq3mpS5cX1GF/QQGfNDbCWxi2g+brxBa+vGcQynW eiS9VQmadgTPi+lDdnOIGHKEA6cCrOe1VVYU4W07YxM2psEdojA9XWJR3TcOBi/6Nrjvp ahZbCoULWhITPU84do/aeCZnBhD9odesz9l2WIbgeyuN6Rnr154kQ/yyVoMiOHLvk9xQz Pm6rPQ7ijEfIUdQxEmMW7wUpjHdJ5SK3HzGneLwPo4UCW2SXR2YiJr4CU60JyK8J410Vp 4T2eGPscW6O6gzobwVEJEO8A6RO7mc4S9SsQTP69o97cQnKOLmZKwaFDK+xNEo4DhDg== ARC-Message-Signature: i=1; d=subspace.kernel.org; s=arc20261015; a=rsa-sha256; c=relaxed/relaxed; t=1792838326; h=Received:DKIM-Signature:From:Date:Subject:MIME-Version:Content-Type: Content-Transfer-Encoding:Message-Id:References:In-Reply-To:To:Cc: X-Mailer:X-Developer-Signature:X-Developer-Key; bh=yWwT+kP05w2NBKJwPtdQDFBE6/jAoeiFgQsCR3SGh/Q=; b=Q0tNLidZVUBc9A8S2GMLuLwEbd1gSWjIHHM+gm0iPJ0OahBphxuQ29YpM0G4QhQ/ez7d dx8KF51iDfBhxfBJx6uYLRnAuw/gJYTGPAiOZmaPfYr+CPS9AE3rllUFv7cP/87EioiZI GqRR6NZP8t8a0HQD9MXyKiJ9lz/aA24QAhAENuwN9l0ZEFZQbSzAqnVnd6HxtIFuHX9zN TMMaysAvQrpoZBtYEx6VSg9KqXt/H9pASmYWpz2te4iOqS5tZiPeAeKaHp2BoRfvyNxot t2pTPXlU9nXM87wpjMT1gZ7n1k5uMsc8SHRdl4Fl73jvaJ5QTJ3/qHq56vGJJSnX2Fw== ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=MpKWtfHY; arc=none smtp.remote-ip=100.103.45.18 Received: from smtp.kernel.org (quasi.space.kernel.org [100.103.45.18]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange X25519 server-signature ECDSA (prime256v1)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3CC13606A31D; Sat, 24 Oct 2026 10:38:45 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key, unprotected) header.d=kernel.org header.i=@kernel.org header.a=rsa-sha256 header.s=k20260515 header.b=MpKWtfHY Received: by smtp.kernel.org (Postfix) with ESMTPSA id DA0B11F01829; Sat, 24 Oct 2026 10:38:43 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=kernel.org; s=k20260515; t=1792838325; bh=yWwT+kP05w2NBKJwPtdQDFBE6/jAoeiFgQsCR3SGh/Q=; h=From:Date:Subject:References:In-Reply-To:To:Cc; b=MpKWtfHYawCai2OgwdZgrBkDMmO61mHxOZ7xqChZbDTl8Fzsu38yv6kveM41//Wiy XQPCLAPjWP4s4YKl1Iw4Vv8MPMdHYWUlQLb9rAEkarvHRHRBxnsiBdnyofnT7Y5RqG XVouVVAYmZd7sOOAka3A8f1+8JN3yVVgZngM5XoXEynKGpPnN+ckgoqYebx7qeaoW6 5m2JWx/k4+QyELVfEE81xAP/FJz7x48qjiM7JsJqORx0XDjV3zFxaxqc/DhTDEZtyU xgJMcHdmdpDeXg1raJXM/uHL5TzlsY32ZQP16WQ3orNY+w0HapwPuhW+18NrxNBvBW r8RYQzXLfY0JA== From: Christian Brauner Date: Sat, 24 Oct 2026 12:38:27 +0200 Subject: [PATCH 1/7] proc: don't let an entry below /proc//net be a mountpoint X-Mailing-List: linux-fsdevel@vger.kernel.org List-Id: List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: 7bit Message-Id: <20261024-work-procfs-fixes-v1-1-46b03c3a8fac@kernel.org> References: <20261024-work-procfs-fixes-v1-0-46b03c3a8fac@kernel.org> In-Reply-To: <20261024-work-procfs-fixes-v1-0-46b03c3a8fac@kernel.org> To: linux-fsdevel@vger.kernel.org Cc: linux-afs@lists.infradead.org, linux-nfs@vger.kernel.org, Alexander Viro , Jan Kara , Aleksa Sarai , Chris Mason , "Christian Brauner (Amutable)" , stable@vger.kernel.org X-Mailer: b4 0.17-dev-ce4c1 X-Developer-Signature: v=1; a=openpgp-sha256; l=7492; i=brauner@kernel.org; h=from:subject:message-id; bh=e+uwrBNgPx9bcNUPhir+thevzEAZ1nCjrVmGPoEc4hA=; b=owGbwMvMwCU28Zj0gdSKO4sYT6slMWTd6Vq303DNatNZTatcyyMv+xtPZfO+OmX9+tOpUx7Md rU/ybShp6OUhUGMi0FWTJHFod0kXG45T8Vmo0wNmDmsTCBDGLg4BWAi77UZ/iceE9PqbTSZ/5x1 yeGXO6K0Ga3rdF+9vnt/bcnEzsD7d/4z/M9yPXIuhcticV3U6vfhLVN/WehO3hHXIbP/kZTQP+n Ss0wA X-Developer-Key: i=brauner@kernel.org; a=openpgp; fpr=4880B8C9BD0E5106FC070F4F7B3C391EFEA93624 proc_net_d_revalidate() always returns 0 so that a dentry below /proc//net is never reused after setns(). So lookup_fast() will end up calling d_invalidate(), which unhashes the dentry and detaches every mount on it, in every mount namespace; including locked mounts. Then lookup_slow() creates a new dentry for the same entry. So a mount on /proc//net/dev succeeds and goes away with the first lookup of the name by anybody with search permission on the directory. Given it's 0555 that's everyone's grandma. A mask on such a name hides nothing as the repro below shows. Treat those entries like I did fd/, fdinfo/ and map_files/ and refuse to mount on them in the first place. It's nonsensical theater otherwise: procnet net/dev stat // SPDX-License-Identifier: GPL-2.0 /* * procnet: cover an entry of /proc// with a bind mount, lock the mount, * then look the name up. * * The first task stays alive, is its pid. The "container" task has its * own mount namespace. There it binds a mask on /proc//. It drops to * uid 1000 and enters a new user namespace with a new mount namespace, so that * the mask is locked. Then one task looks the name up: * * stat the container calls stat() on the name * umount the container calls umount2(MNT_DETACH) on the name * nobody a task with uid 65534 in the first namespaces calls stat() * * The container lists its mounts before and after (that does not look the * name up) and then reads the name. MASK is fine. Anything else means that * the lookup uncovered the entry. * * usage: procnet stat|umount|nobody * name "status" is the control, "net/dev" is a file below net, "net/stat" is * a directory below net */ #define _GNU_SOURCE #include #include #include #include #include #include #include #include #include #include #include #include #include #define WORK "/run/pn" static char target[128]; static void die(const char *what) { fprintf(stderr, "FATAL %s: %s\n", what, strerror(errno)); exit(2); } static void write_str(const char *path, const char *s, int flags) { int fd = open(path, O_WRONLY | flags, 0644); if (fd < 0 || write(fd, s, strlen(s)) < 0) die(path); close(fd); } /* list the mounts below /proc without a lookup of the target */ static void mounts(const char *when) { char line[512]; FILE *f = fopen("/proc/self/mountinfo", "r"); int n = 0; if (!f) die("mountinfo"); while (fgets(line, sizeof(line), f)) { if (!strstr(line, " /proc/")) continue; printf("container: %s: mount: %s", when, line); n++; } fclose(f); if (!n) printf("container: %s: nothing is mounted below /proc\n", when); } static void show(const char *when, int isdir) { char buf[48] = ""; int fd; if (isdir) { struct dirent *de; DIR *d = opendir(target); int hit = 0; if (!d) { printf("container: %s: opendir(%s) = %s\n", when, target, strerror(errno)); return; } printf("container: %s: readdir(%s) =", when, target); while ((de = readdir(d))) { if (de->d_name[0] == '.') continue; printf(" %s", de->d_name); hit |= strcmp(de->d_name, "MASK"); } closedir(d); printf("%s\n", hit ? " <-- HIT" : ""); return; } fd = open(target, O_RDONLY); if (fd < 0) { printf("container: %s: open(%s) = %s\n", when, target, strerror(errno)); return; } if (read(fd, buf, sizeof(buf) - 1) < 0) die("read"); close(fd); if (strchr(buf, '\n')) *strchr(buf, '\n') = 0; printf("container: %s: read(%s) = \"%s\"%s\n", when, target, buf, strncmp(buf, "MASK", 4) ? " <-- HIT" : ""); } int main(int argc, char **argv) { const char *name = argc > 1 ? argv[1] : "net/dev"; const char *op = argc > 2 ? argv[2] : "stat"; int isdir = !strcmp(name, "net/stat"); int to_host[2], to_cont[2]; struct stat st; pid_t pid; char c; setvbuf(stdout, NULL, _IOLBF, 0); snprintf(target, sizeof(target), "/proc/%d/%s", getpid(), name); if (unshare(CLONE_NEWNS) || mount(NULL, "/", NULL, MS_REC | MS_PRIVATE, NULL)) die("unshare"); mkdir(WORK, 0755); if (mount("W", WORK, "tmpfs", 0, NULL)) die("mount W"); write_str(WORK "/mask", "MASK", O_CREAT); mkdir(WORK "/maskdir", 0755); write_str(WORK "/maskdir/MASK", "MASK", O_CREAT); if (pipe(to_host) || pipe(to_cont)) die("pipe"); pid = fork(); if (pid < 0) die("fork"); if (!pid) { if (unshare(CLONE_NEWNS)) die("container unshare"); if (mount(isdir ? WORK "/maskdir" : WORK "/mask", target, NULL, MS_BIND, NULL)) { printf("container: bind the mask on %s: %s\n", target, strerror(errno)); exit(0); } printf("container: bind the mask on %s: ok\n", target); if (setgid(1000) || setuid(1000) || prctl(PR_SET_DUMPABLE, 1)) die("setuid"); /* lock the mount: a new user namespace with a copy of the tree */ if (unshare(CLONE_NEWUSER | CLONE_NEWNS)) die("container userns"); write_str("/proc/self/setgroups", "deny", 0); write_str("/proc/self/uid_map", "0 1000 1", 0); write_str("/proc/self/gid_map", "0 1000 1", 0); mounts("before"); errno = 0; if (!strcmp(op, "stat")) { stat(target, &st); printf("container: stat(%s): %s\n", target, strerror(errno)); } else if (!strcmp(op, "umount")) { umount2(target, MNT_DETACH); printf("container: umount2(%s, MNT_DETACH): %s\n", target, strerror(errno)); } else if (write(to_host[1], "g", 1) != 1 || read(to_cont[0], &c, 1) != 1) { die("sync"); } mounts("after"); show("after", isdir); exit(0); } close(to_host[1]); if (read(to_host[0], &c, 1) == 1) { pid_t p2 = fork(); if (!p2) { if (setgid(65534) || setuid(65534)) die("setuid"); errno = 0; stat(target, &st); printf("nobody: uid %u: stat(%s): %s\n", getuid(), target, strerror(errno)); exit(0); } waitpid(p2, NULL, 0); if (write(to_cont[1], "g", 1) != 1) die("sync"); } waitpid(pid, NULL, 0); return 0; } Fixes: 1fde6f21d90f ("proc: fix /proc/net/* after setns(2)") Cc: Signed-off-by: Christian Brauner (Amutable) --- fs/proc/generic.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/fs/proc/generic.c b/fs/proc/generic.c index 2b1971da4a85..4c3fcaf68ef4 100644 --- a/fs/proc/generic.c +++ b/fs/proc/generic.c @@ -256,9 +256,10 @@ struct dentry *proc_lookup_de(struct inode *dir, struct dentry *dentry, inode = proc_get_inode(dir->i_sb, de); if (!inode) return ERR_PTR(-ENOMEM); + /* never reused and so never a mountpoint for long */ if (de->flags & PROC_ENTRY_FORCE_LOOKUP) - return d_splice_alias_ops(inode, dentry, - &proc_net_dentry_ops); + return proc_splice_unmountable(inode, dentry, + &proc_net_dentry_ops); return d_splice_alias_ops(inode, dentry, &proc_misc_dentry_ops); } -- 2.53.0