Thread (20 messages) 20 messages, 4 authors, 2d ago
WARM2d

[PATCH] ovl: add ioctls to retrieve layer file descriptors

From: Giuseppe Scrivano <hidden>
Date: 2026-07-08 09:58:41
Also in: linux-fsdevel, linux-unionfs
Subsystem: filesystems (vfs and infrastructure), overlay filesystem, the rest · Maintainers: Alexander Viro, Christian Brauner, Miklos Szeredi, Amir Goldstein, Linus Torvalds

Add two ioctls to overlay filesystem to allow userspace to retrieve
information about the overlay layers:

OVL_IOC_OPEN_LAYER:     return an O_PATH fd to the root of a layer.
                        arg == 0 returns the upper layer (-ENOENT if
                        no upper is configured), arg >= 1 returns
                        lower layers (-ENOENT if index is out of
                        range).
OVL_IOC_GET_LAYERS_INFO: copy a struct ovl_layers_info to userspace
                        with numlower, numlowerdata, and has_upper.

The ioctls work on any overlayfs file or directory and require
CAP_SYS_ADMIN in the mounter's user namespace.

The UAPI constants and struct are defined in include/uapi/linux/overlay.h.

Signed-off-by: Giuseppe Scrivano <redacted>
---
 MAINTAINERS                  |  1 +
 fs/overlayfs/file.c          |  2 ++
 fs/overlayfs/overlayfs.h     |  4 +++
 fs/overlayfs/ovl_entry.h     |  2 ++
 fs/overlayfs/params.c        | 10 ++++++
 fs/overlayfs/params.h        |  1 +
 fs/overlayfs/readdir.c       |  2 ++
 fs/overlayfs/super.c         | 70 ++++++++++++++++++++++++++++++++++++
 include/uapi/linux/overlay.h | 30 ++++++++++++++++
 9 files changed, 122 insertions(+)
 create mode 100644 include/uapi/linux/overlay.h
diff --git a/MAINTAINERS b/MAINTAINERS
index 25453040dffb..b64c696686e4 100644
--- a/MAINTAINERS
+++ b/MAINTAINERS
@@ -20368,6 +20368,7 @@ S:	Supported
 T:	git git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs.git
 F:	Documentation/filesystems/overlayfs.rst
 F:	fs/overlayfs/
+F:	include/uapi/linux/overlay.h
 
 P54 WIRELESS DRIVER
 M:	Christian Lamparter <chunkeey@googlemail.com>
diff --git a/fs/overlayfs/file.c b/fs/overlayfs/file.c
index 27cc07738f33..fc9c448c5959 100644
--- a/fs/overlayfs/file.c
+++ b/fs/overlayfs/file.c
@@ -649,4 +649,6 @@ const struct file_operations ovl_file_operations = {
 	.copy_file_range	= ovl_copy_file_range,
 	.remap_file_range	= ovl_remap_file_range,
 	.setlease		= generic_setlease,
+	.unlocked_ioctl		= ovl_ioctl,
+	.compat_ioctl		= compat_ptr_ioctl,
 };
diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h
index b75df37f70ac..10f7ef8cb78c 100644
--- a/fs/overlayfs/overlayfs.h
+++ b/fs/overlayfs/overlayfs.h
@@ -8,6 +8,7 @@
 #include <linux/uuid.h>
 #include <linux/fs.h>
 #include <linux/fsverity.h>
+#include <uapi/linux/overlay.h>
 #include <linux/namei.h>
 #include <linux/posix_acl.h>
 #include <linux/posix_acl_xattr.h>
@@ -908,6 +909,9 @@ void ovl_tempname(char name[OVL_TEMPNAME_SIZE]);
 struct dentry *ovl_create_temp(struct ovl_fs *ofs, struct dentry *workdir,
 			       struct ovl_cattr *attr);
 
+/* super.c */
+long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg);
+
 /* file.c */
 extern const struct file_operations ovl_file_operations;
 int ovl_real_fileattr_get(const struct path *realpath, struct file_kattr *fa);
diff --git a/fs/overlayfs/ovl_entry.h b/fs/overlayfs/ovl_entry.h
index 80cad4ea96a3..b8f4bca89a27 100644
--- a/fs/overlayfs/ovl_entry.h
+++ b/fs/overlayfs/ovl_entry.h
@@ -35,6 +35,8 @@ struct ovl_layer {
 	struct vfsmount *mnt;
 	/* Trap in ovl inode cache */
 	struct inode *trap;
+	/* Keeps the original fsmount file alive for OVL_IOC_OPEN_LAYER */
+	struct file *origin;
 	struct ovl_sb *fs;
 	/* Index of this layer in fs root (upper idx == 0) */
 	int idx;
diff --git a/fs/overlayfs/params.c b/fs/overlayfs/params.c
index c93fcaa45d4a..92d1a56178f2 100644
--- a/fs/overlayfs/params.c
+++ b/fs/overlayfs/params.c
@@ -482,6 +482,11 @@ static int ovl_parse_layer(struct fs_context *fc, struct fs_parameter *param,
 			return PTR_ERR(layer_name);
 
 		err = ovl_do_parse_layer(fc, layer_name, &layer_path, layer);
+		if (!err && !is_upper_layer(layer)) {
+			struct ovl_fs_context *ctx = fc->fs_private;
+
+			ctx->lower[ctx->nr - 1].origin = get_file(param->file);
+		}
 		break;
 	}
 	default:
@@ -504,6 +509,9 @@ static void ovl_reset_lowerdirs(struct ovl_fs_context *ctx)
 		path_put(&l->path);
 		kfree(l->name);
 		l->name = NULL;
+		if (l->origin)
+			fput(l->origin);
+		l->origin = NULL;
 	}
 	ctx->nr = 0;
 	ctx->nr_data = 0;
@@ -856,6 +864,8 @@ void ovl_free_fs(struct ovl_fs *ofs)
 	mounts = (struct vfsmount **) ofs->config.lowerdirs;
 	for (i = 0; i < ofs->numlayer; i++) {
 		iput(ofs->layers[i].trap);
+		if (ofs->layers[i].origin)
+			fput(ofs->layers[i].origin);
 		kfree(ofs->config.lowerdirs[i]);
 		mounts[i] = ofs->layers[i].mnt;
 	}
diff --git a/fs/overlayfs/params.h b/fs/overlayfs/params.h
index ffd53cdd8482..1d8fe8fbaca2 100644
--- a/fs/overlayfs/params.h
+++ b/fs/overlayfs/params.h
@@ -22,6 +22,7 @@ struct ovl_opt_set {
 struct ovl_fs_context_layer {
 	char *name;
 	struct path path;
+	struct file *origin;
 };
 
 struct ovl_fs_context {
diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c
index e7fe29cb6028..7bab71c8bcc2 100644
--- a/fs/overlayfs/readdir.c
+++ b/fs/overlayfs/readdir.c
@@ -1069,6 +1069,8 @@ const struct file_operations ovl_dir_operations = {
 	.iterate_shared	= shared_ovl_iterate,
 	.llseek		= ovl_dir_llseek,
 	.fsync		= ovl_dir_fsync,
+	.unlocked_ioctl	= ovl_ioctl,
+	.compat_ioctl	= compat_ptr_ioctl,
 	.release	= ovl_dir_release,
 	.setlease	= generic_setlease,
 };
diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index 60f0b7ceef0a..d143002e74b2 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -1107,6 +1107,8 @@ static int ovl_get_layers(struct super_block *sb, struct ovl_fs *ofs,
 		 */
 		mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
 
+		layers[ofs->numlayer].origin = l->origin;
+		l->origin = NULL;
 		layers[ofs->numlayer].trap = trap;
 		layers[ofs->numlayer].mnt = mnt;
 		layers[ofs->numlayer].idx = ofs->numlayer;
@@ -1568,6 +1570,74 @@ int ovl_fill_super(struct super_block *sb, struct fs_context *fc)
 	return err;
 }
 
+static long ovl_ioctl_open_layer(struct file *filp, unsigned long arg)
+{
+	struct super_block *sb = file_inode(filp)->i_sb;
+	struct ovl_fs *ofs = OVL_FS(sb);
+	struct path root;
+	struct file *f;
+	int fd;
+
+	if (arg >= ofs->numlayer)
+		return -ENOENT;
+	if (arg == 0 && !ovl_upper_mnt(ofs))
+		return -ENOENT;
+	if (!ofs->layers[arg].origin)
+		return -EOPNOTSUPP;
+
+	root.mnt = mntget(ofs->layers[arg].origin->f_path.mnt);
+	root.dentry = dget(root.mnt->mnt_root);
+
+	fd = get_unused_fd_flags(O_CLOEXEC);
+	if (fd < 0) {
+		path_put(&root);
+		return fd;
+	}
+
+	f = dentry_open(&root, O_PATH | O_NOFOLLOW, current_cred());
+	path_put(&root);
+	if (IS_ERR(f)) {
+		put_unused_fd(fd);
+		return PTR_ERR(f);
+	}
+
+	fd_install(fd, f);
+	return fd;
+}
+
+static long ovl_ioctl_get_layers_info(struct file *filp, unsigned long arg)
+{
+	struct super_block *sb = file_inode(filp)->i_sb;
+	struct ovl_fs *ofs = OVL_FS(sb);
+	struct ovl_layers_info info = {
+		.numlower = ofs->numlayer - 1,
+		.numlowerdata = ofs->numdatalayer,
+		.has_upper = !!ovl_upper_mnt(ofs),
+	};
+
+	if (copy_to_user((void __user *)arg, &info, sizeof(info)))
+		return -EFAULT;
+
+	return 0;
+}
+
+long ovl_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
+{
+	struct ovl_fs *ofs = OVL_FS(file_inode(filp)->i_sb);
+
+	if (!ns_capable(ofs->creator_cred->user_ns, CAP_SYS_ADMIN))
+		return -EPERM;
+
+	switch (cmd) {
+	case OVL_IOC_OPEN_LAYER:
+		return ovl_ioctl_open_layer(filp, arg);
+	case OVL_IOC_GET_LAYERS_INFO:
+		return ovl_ioctl_get_layers_info(filp, arg);
+	default:
+		return -ENOTTY;
+	}
+}
+
 struct file_system_type ovl_fs_type = {
 	.owner			= THIS_MODULE,
 	.name			= "overlay",
diff --git a/include/uapi/linux/overlay.h b/include/uapi/linux/overlay.h
new file mode 100644
index 000000000000..c92ccecd9e21
--- /dev/null
+++ b/include/uapi/linux/overlay.h
@@ -0,0 +1,30 @@
+/* SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note */
+#ifndef _UAPI_LINUX_OVERLAY_H
+#define _UAPI_LINUX_OVERLAY_H
+
+#include <linux/ioctl.h>
+#include <linux/types.h>
+
+/**
+ * struct ovl_layers_info - overlay layer configuration summary
+ * @numlower:     number of lower (metadata) layers
+ * @numlowerdata: number of data-only lower layers
+ * @has_upper:    1 if an upper layer is configured, 0 otherwise
+ */
+struct ovl_layers_info {
+	__u32 numlower;
+	__u32 numlowerdata;
+	__u32 has_upper;
+};
+
+/*
+ * Return an O_PATH fd to the root of the specified overlay layer.
+ * arg == 0: upper layer (returns -ENOENT if no upper is configured)
+ * arg >= 1: lower layers (returns -ENOENT if index is out of range)
+ */
+#define OVL_IOC_OPEN_LAYER	_IO('O', 1)
+
+/* Retrieve overlay layer configuration into struct ovl_layers_info. */
+#define OVL_IOC_GET_LAYERS_INFO	_IOR('O', 2, struct ovl_layers_info)
+
+#endif /* _UAPI_LINUX_OVERLAY_H */
-- 
2.55.0
Keyboard shortcuts
hback out one level
jnext message in thread
kprevious message in thread
ldrill in
Escclose help / fold thread tree
?toggle this help