[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