From: Christian Brauner <brauner@kernel.org>
To: Jan Kara <jack@suse.cz>, Amir Goldstein <amir73il@gmail.com>,
	 linux-fsdevel@vger.kernel.org
Cc: "Josef Bacik" <josef@toxicpanda.com>,
	"Jeff Layton" <jlayton@kernel.org>, "Mike Yuan" <me@yhndnzj.com>,
	"Zbigniew Jędrzejewski-Szmek" <zbyszek@in.waw.pl>,
	"Lennart Poettering" <mzxreary@0pointer.de>,
	"Daan De Meyer" <daan.j.demeyer@gmail.com>,
	"Aleksa Sarai" <cyphar@cyphar.com>,
	"Alexander Viro" <viro@zeniv.linux.org.uk>,
	"Jens Axboe" <axboe@kernel.dk>, "Tejun Heo" <tj@kernel.org>,
	"Johannes Weiner" <hannes@cmpxchg.org>,
	"Michal Koutný" <mkoutny@suse.com>,
	"Eric Dumazet" <edumazet@google.com>,
	"Jakub Kicinski" <kuba@kernel.org>,
	"Paolo Abeni" <pabeni@redhat.com>,
	"Simon Horman" <horms@kernel.org>,
	"Chuck Lever" <chuck.lever@oracle.com>,
	linux-nfs@vger.kernel.org, linux-kselftest@vger.kernel.org,
	linux-block@vger.kernel.org, linux-kernel@vger.kernel.org,
	cgroups@vger.kernel.org, netdev@vger.kernel.org,
	"Christian Brauner" <brauner@kernel.org>
Subject: [PATCH 27/32] nsfs: support file handles
Date: Wed, 10 Sep 2025 16:37:12 +0200	[thread overview]
Message-ID: <20250910-work-namespace-v1-27-4dd56e7359d8@kernel.org> (raw)
In-Reply-To: <20250910-work-namespace-v1-0-4dd56e7359d8@kernel.org>
A while ago we added support for file handles to pidfs so pidfds can be
encoded and decoded as file handles. Userspace has adopted this quickly
and it's proven very useful. Pidfd file handles are exhaustive meaning
they don't require a handle on another pidfd to pass to
open_by_handle_at() so it can derive the filesystem to decode in.
Implement the exhaustive file handles for namespaces as well.
Signed-off-by: Christian Brauner <brauner@kernel.org>
---
 fs/nsfs.c                | 176 +++++++++++++++++++++++++++++++++++++++++++++++
 include/linux/exportfs.h |   6 ++
 2 files changed, 182 insertions(+)
diff --git a/fs/nsfs.c b/fs/nsfs.c
index 6f8008177133..a1585a2f4f03 100644
--- a/fs/nsfs.c
+++ b/fs/nsfs.c
@@ -13,6 +13,12 @@
 #include <linux/nsfs.h>
 #include <linux/uaccess.h>
 #include <linux/mnt_namespace.h>
+#include <linux/ipc_namespace.h>
+#include <linux/time_namespace.h>
+#include <linux/utsname.h>
+#include <linux/exportfs.h>
+#include <linux/nstree.h>
+#include <net/net_namespace.h>
 
 #include "mount.h"
 #include "internal.h"
@@ -417,12 +423,182 @@ static const struct stashed_operations nsfs_stashed_ops = {
 	.put_data = nsfs_put_data,
 };
 
+struct nsfs_fid {
+	u64 ns_id;
+	u32 ns_type;
+	u32 ns_inum;
+} __attribute__ ((packed));
+
+#define NSFS_FID_SIZE (sizeof(struct nsfs_fid) / sizeof(u32))
+
+static int nsfs_encode_fh(struct inode *inode, u32 *fh, int *max_len,
+			  struct inode *parent)
+{
+	struct nsfs_fid *fid = (struct nsfs_fid *)fh;
+	struct ns_common *ns = inode->i_private;
+	int len = *max_len;
+
+	/*
+	 * TODO:
+	 * For hierarchical namespaces we should start to encode the
+	 * parent namespace. Then userspace can walk a namespace
+	 * hierarchy purely based on file handles.
+	 */
+	if (parent)
+		return FILEID_INVALID;
+
+	if (len < NSFS_FID_SIZE) {
+		*max_len = NSFS_FID_SIZE;
+		return FILEID_INVALID;
+	}
+
+	len  = NSFS_FID_SIZE;
+
+	fid->ns_id = ns->ns_id;
+	fid->ns_type = ns->ops->type;
+	fid->ns_inum = inode->i_ino;
+	*max_len = len;
+	return FILEID_NSFS;
+}
+
+static struct dentry *nsfs_fh_to_dentry(struct super_block *sb, struct fid *fh,
+					int fh_len, int fh_type)
+{
+	struct path path __free(path_put) = {};
+	struct nsfs_fid *fid = (struct nsfs_fid *)fh;
+	struct user_namespace *owning_ns = NULL;
+	struct ns_common *ns;
+	int ret;
+
+	if (fh_len < NSFS_FID_SIZE)
+		return NULL;
+
+	switch (fh_type) {
+	case FILEID_NSFS:
+		break;
+	default:
+		return NULL;
+	}
+
+	scoped_guard(rcu) {
+		ns = ns_tree_lookup_rcu(fid->ns_id, fid->ns_type);
+		if (!ns)
+			return NULL;
+
+		VFS_WARN_ON_ONCE(ns->ns_id != fid->ns_id);
+		VFS_WARN_ON_ONCE(ns->ops->type != fid->ns_type);
+		VFS_WARN_ON_ONCE(ns->inum != fid->ns_inum);
+
+		if (!refcount_inc_not_zero(&ns->count))
+			return NULL;
+	}
+
+	switch (ns->ops->type) {
+#ifdef CONFIG_CGROUPS
+	case CLONE_NEWCGROUP:
+		if (!current_in_namespace(to_cg_ns(ns)))
+			owning_ns = to_cg_ns(ns)->user_ns;
+		break;
+#endif
+#ifdef CONFIG_IPC_NS
+	case CLONE_NEWIPC:
+		if (!current_in_namespace(to_ipc_ns(ns)))
+			owning_ns = to_ipc_ns(ns)->user_ns;
+		break;
+#endif
+	case CLONE_NEWNS:
+		if (!current_in_namespace(to_mnt_ns(ns)))
+			owning_ns = to_mnt_ns(ns)->user_ns;
+		break;
+#ifdef CONFIG_NET_NS
+	case CLONE_NEWNET:
+		if (!current_in_namespace(to_net_ns(ns)))
+			owning_ns = to_net_ns(ns)->user_ns;
+		break;
+#endif
+#ifdef CONFIG_PID_NS
+	case CLONE_NEWPID:
+		if (!current_in_namespace(to_pid_ns(ns))) {
+			owning_ns = to_pid_ns(ns)->user_ns;
+		} else if (!READ_ONCE(to_pid_ns(ns)->child_reaper)) {
+			ns->ops->put(ns);
+			return ERR_PTR(-EPERM);
+		}
+		break;
+#endif
+#ifdef CONFIG_TIME_NS
+	case CLONE_NEWTIME:
+		if (!current_in_namespace(to_time_ns(ns)))
+			owning_ns = to_time_ns(ns)->user_ns;
+		break;
+#endif
+#ifdef CONFIG_USER_NS
+	case CLONE_NEWUSER:
+		if (!current_in_namespace(to_user_ns(ns)))
+			owning_ns = to_user_ns(ns);
+		break;
+#endif
+#ifdef CONFIG_UTS_NS
+	case CLONE_NEWUTS:
+		if (!current_in_namespace(to_uts_ns(ns)))
+			owning_ns = to_uts_ns(ns)->user_ns;
+		break;
+#endif
+	default:
+		return ERR_PTR(-EOPNOTSUPP);
+	}
+
+	if (owning_ns && !ns_capable(owning_ns, CAP_SYS_ADMIN)) {
+		ns->ops->put(ns);
+		return ERR_PTR(-EPERM);
+	}
+
+	/* path_from_stashed() unconditionally consumes the reference. */
+	ret = path_from_stashed(&ns->stashed, nsfs_mnt, ns, &path);
+	if (ret)
+		return ERR_PTR(ret);
+
+	return no_free_ptr(path.dentry);
+}
+
+/*
+ * Make sure that we reject any nonsensical flags that users pass via
+ * open_by_handle_at().
+ */
+#define VALID_FILE_HANDLE_OPEN_FLAGS \
+	(O_RDONLY | O_WRONLY | O_RDWR | O_NONBLOCK | O_CLOEXEC | O_EXCL)
+
+static int nsfs_export_permission(struct handle_to_path_ctx *ctx,
+				   unsigned int oflags)
+{
+	if (oflags & ~(VALID_FILE_HANDLE_OPEN_FLAGS | O_LARGEFILE))
+		return -EINVAL;
+
+	/* nsfs_fh_to_dentry() is performs further permission checks. */
+	return 0;
+}
+
+static struct file *nsfs_export_open(struct path *path, unsigned int oflags)
+{
+	/* Clear O_LARGEFILE as open_by_handle_at() forces it. */
+	oflags &= ~O_LARGEFILE;
+	return file_open_root(path, "", oflags, 0);
+}
+
+static const struct export_operations nsfs_export_operations = {
+	.encode_fh	= nsfs_encode_fh,
+	.fh_to_dentry	= nsfs_fh_to_dentry,
+	.open		= nsfs_export_open,
+	.permission	= nsfs_export_permission,
+};
+
 static int nsfs_init_fs_context(struct fs_context *fc)
 {
 	struct pseudo_fs_context *ctx = init_pseudo(fc, NSFS_MAGIC);
 	if (!ctx)
 		return -ENOMEM;
 	ctx->ops = &nsfs_ops;
+	ctx->eops = &nsfs_export_operations;
 	ctx->dops = &ns_dentry_operations;
 	fc->s_fs_info = (void *)&nsfs_stashed_ops;
 	return 0;
diff --git a/include/linux/exportfs.h b/include/linux/exportfs.h
index cfb0dd1ea49c..3aac58a520c7 100644
--- a/include/linux/exportfs.h
+++ b/include/linux/exportfs.h
@@ -122,6 +122,12 @@ enum fid_type {
 	FILEID_BCACHEFS_WITHOUT_PARENT = 0xb1,
 	FILEID_BCACHEFS_WITH_PARENT = 0xb2,
 
+	/*
+	 *
+	 * 64 bit namespace identifier, 32 bit namespace type, 32 bit inode number.
+	 */
+	FILEID_NSFS = 0xf1,
+
 	/*
 	 * 64 bit unique kernfs id
 	 */
-- 
2.47.3
next prev parent reply	other threads:[~2025-09-10 14:39 UTC|newest]
Thread overview: 78+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2025-09-10 14:36 [PATCH 00/32] ns: support file handles Christian Brauner
2025-09-10 14:36 ` [PATCH 01/32] pidfs: validate extensible ioctls Christian Brauner
2025-09-10 15:33   ` Jan Kara
2025-09-10 16:33   ` Aleksa Sarai
2025-10-23 10:46   ` Jiri Slaby
2025-10-24 22:31     ` Jan Kara
2025-09-10 14:36 ` [PATCH 02/32] nsfs: " Christian Brauner
2025-09-10 15:34   ` Jan Kara
2025-09-10 14:36 ` [PATCH 03/32] block: use extensible_ioctl_valid() Christian Brauner
2025-09-10 15:34   ` Jan Kara
2025-09-10 16:39   ` Jens Axboe
2025-09-10 14:36 ` [PATCH 04/32] ns: move to_ns_common() to ns_common.h Christian Brauner
2025-09-10 15:36   ` Jan Kara
2025-09-10 14:36 ` [PATCH 05/32] nsfs: add nsfs.h header Christian Brauner
2025-09-10 15:37   ` Jan Kara
2025-09-10 14:36 ` [PATCH 06/32] ns: uniformly initialize ns_common Christian Brauner
2025-09-10 15:40   ` Jan Kara
2025-09-10 14:36 ` [PATCH 07/32] mnt: use ns_common_init() Christian Brauner
2025-09-10 15:40   ` Jan Kara
2025-09-10 14:36 ` [PATCH 08/32] ipc: " Christian Brauner
2025-09-10 15:40   ` Jan Kara
2025-09-10 14:36 ` [PATCH 09/32] cgroup: " Christian Brauner
2025-09-10 15:42   ` Jan Kara
2025-09-10 14:36 ` [PATCH 10/32] pid: " Christian Brauner
2025-09-10 15:42   ` Jan Kara
2025-09-10 14:36 ` [PATCH 11/32] time: " Christian Brauner
2025-09-10 15:18   ` Thomas Gleixner
2025-09-10 15:44   ` Jan Kara
2025-09-10 14:36 ` [PATCH 12/32] uts: " Christian Brauner
2025-09-10 15:46   ` Jan Kara
2025-09-10 14:36 ` [PATCH 13/32] user: " Christian Brauner
2025-09-10 15:46   ` Jan Kara
2025-09-10 14:36 ` [PATCH 14/32] net: " Christian Brauner
2025-09-10 15:57   ` Jan Kara
2025-09-11  8:46     ` Christian Brauner
2025-09-11  9:19       ` Jan Kara
2025-09-10 21:07   ` Sasha Levin
2025-09-10 14:37 ` [PATCH 15/32] ns: remove ns_alloc_inum() Christian Brauner
2025-09-10 15:48   ` Jan Kara
2025-09-10 14:37 ` [PATCH 16/32] nstree: make iterator generic Christian Brauner
2025-09-10 14:37 ` [PATCH 17/32] mnt: support iterator Christian Brauner
2025-09-18  0:46   ` Askar Safin
2025-09-10 14:37 ` [PATCH 18/32] cgroup: " Christian Brauner
2025-09-10 16:48   ` Tejun Heo
2025-09-10 14:37 ` [PATCH 19/32] ipc: " Christian Brauner
2025-09-10 14:37 ` [PATCH 20/32] net: " Christian Brauner
2025-09-10 14:37 ` [PATCH 21/32] pid: " Christian Brauner
2025-09-10 14:37 ` [PATCH 22/32] time: " Christian Brauner
2025-09-10 15:19   ` Thomas Gleixner
2025-09-10 14:37 ` [PATCH 23/32] userns: " Christian Brauner
2025-09-10 14:37 ` [PATCH 24/32] uts: " Christian Brauner
2025-09-10 14:37 ` [PATCH 25/32] ns: add to_<type>_ns() to respective headers Christian Brauner
2025-09-10 16:35   ` Aleksa Sarai
2025-09-21  7:35   ` Thomas Gleixner
2025-09-10 14:37 ` [PATCH 26/32] nsfs: add current_in_namespace() Christian Brauner
2025-09-10 16:38   ` Aleksa Sarai
2025-09-10 14:37 ` Christian Brauner [this message]
2025-09-10 17:21   ` [PATCH 27/32] nsfs: support file handles Amir Goldstein
2025-09-11  9:31     ` Christian Brauner
2025-09-11 11:36       ` Amir Goldstein
2025-09-12  8:19         ` Christian Brauner
2025-09-12  9:12           ` Amir Goldstein
2025-09-18  3:40           ` Aleksa Sarai
2025-09-10 14:37 ` [PATCH 28/32] nsfs: support exhaustive " Christian Brauner
2025-09-10 17:07   ` Amir Goldstein
2025-09-10 14:37 ` [PATCH 29/32] nsfs: add missing id retrieval support Christian Brauner
2025-09-10 16:49   ` Aleksa Sarai
2025-09-11  7:52     ` Christian Brauner
2025-09-11 12:56       ` Aleksa Sarai
2025-09-10 14:37 ` [PATCH 30/32] tools: update nsfs.h uapi header Christian Brauner
2025-09-10 14:37 ` [PATCH 31/32] selftests/namespaces: add identifier selftests Christian Brauner
2025-09-10 14:37 ` [PATCH 32/32] selftests/namespaces: add file handle selftests Christian Brauner
2025-09-10 17:30   ` Amir Goldstein
2025-09-11  9:15     ` Christian Brauner
2025-09-11 11:48       ` Amir Goldstein
2025-09-10 21:46   ` Bart Van Assche
2025-09-11  8:59     ` Christian Brauner
2025-09-10 20:53 ` [syzbot ci] Re: ns: support file handles syzbot ci
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox
  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):
  git send-email \
    --in-reply-to=20250910-work-namespace-v1-27-4dd56e7359d8@kernel.org \
    --to=brauner@kernel.org \
    --cc=amir73il@gmail.com \
    --cc=axboe@kernel.dk \
    --cc=cgroups@vger.kernel.org \
    --cc=chuck.lever@oracle.com \
    --cc=cyphar@cyphar.com \
    --cc=daan.j.demeyer@gmail.com \
    --cc=edumazet@google.com \
    --cc=hannes@cmpxchg.org \
    --cc=horms@kernel.org \
    --cc=jack@suse.cz \
    --cc=jlayton@kernel.org \
    --cc=josef@toxicpanda.com \
    --cc=kuba@kernel.org \
    --cc=linux-block@vger.kernel.org \
    --cc=linux-fsdevel@vger.kernel.org \
    --cc=linux-kernel@vger.kernel.org \
    --cc=linux-kselftest@vger.kernel.org \
    --cc=linux-nfs@vger.kernel.org \
    --cc=me@yhndnzj.com \
    --cc=mkoutny@suse.com \
    --cc=mzxreary@0pointer.de \
    --cc=netdev@vger.kernel.org \
    --cc=pabeni@redhat.com \
    --cc=tj@kernel.org \
    --cc=viro@zeniv.linux.org.uk \
    --cc=zbyszek@in.waw.pl \
    /path/to/YOUR_REPLY
  https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
  Be sure your reply has a Subject: header at the top and a blank line
  before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).