[PATCH] ovl: disable index and nfs_export with different lower idmaps

Jérémy Jean posted 1 patch 1 week, 6 days ago
fs/overlayfs/super.c | 26 +++++++++++++++++++++++++-
1 file changed, 25 insertions(+), 1 deletion(-)
[PATCH] ovl: disable index and nfs_export with different lower idmaps
Posted by Jérémy Jean 1 week, 6 days ago
Overlay file handles identify a backing filesystem, but not the lower
mount used to decode it. If lower layers share a superblock and use
different idmaps, a file handle can decode through the wrong layer and
initialize the overlay inode with the wrong owner.

Disable index and nfs_export when this layout is found. Both features
rely on persistent lower file handles, which cannot distinguish
same-superblock layers with different idmaps. Without them, OverlayFS
uses normal path lookup and keeps the layer chosen during lookup.

Fixes: bc70682a497c ("ovl: support idmapped layers")
Assisted-by: Codex:gpt-5
Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>
---
 fs/overlayfs/super.c | 26 +++++++++++++++++++++++++-
 1 file changed, 25 insertions(+), 1 deletion(-)

diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
index bd0a3f9039d2..00e1b9fccb4f 100644
--- a/fs/overlayfs/super.c
+++ b/fs/overlayfs/super.c
@@ -944,6 +944,22 @@ static bool ovl_lower_uuid_ok(struct ovl_fs *ofs, const uuid_t *uuid)
 	return true;
 }
 
+static bool ovl_lower_mnt_idmap_mismatch(struct ovl_fs *ofs,
+					 const struct path *path)
+{
+	unsigned int i;
+
+	for (i = 1; i < ofs->numlayer; i++) {
+		struct vfsmount *mnt = ofs->layers[i].mnt;
+
+		if (mnt->mnt_sb == path->mnt->mnt_sb &&
+		    mnt_idmap(mnt) != mnt_idmap(path->mnt))
+			return true;
+	}
+
+	return false;
+}
+
 /* Get a unique fsid for the layer */
 static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path)
 {
@@ -956,8 +972,16 @@ static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path)
 	bool warn = false;
 
 	for (i = 0; i < ofs->numfs; i++) {
-		if (ofs->fs[i].sb == sb)
+		if (ofs->fs[i].sb == sb) {
+			if ((ofs->config.index || ofs->config.nfs_export) &&
+			    ovl_lower_mnt_idmap_mismatch(ofs, path)) {
+				ofs->config.index = false;
+				ofs->config.nfs_export = false;
+				pr_warn("different idmaps in same lower fs '%pd2', falling back to index=off,nfs_export=off.\n",
+					path->dentry);
+			}
 			return i;
+		}
 	}
 
 	if (!ovl_lower_uuid_ok(ofs, uuid)) {
-- 
2.47.3

Re: [PATCH] ovl: disable index and nfs_export with different lower idmaps
Posted by Amir Goldstein 1 week, 5 days ago
On Fri, Sep 11, 2026 at 9:43 PM Jérémy Jean
<Jeremy.Jean@oss.cyber.gouv.fr> wrote:
>
> Overlay file handles identify a backing filesystem, but not the lower
> mount used to decode it. If lower layers share a superblock and use
> different idmaps, a file handle can decode through the wrong layer and
> initialize the overlay inode with the wrong owner.
>
> Disable index and nfs_export when this layout is found. Both features
> rely on persistent lower file handles, which cannot distinguish
> same-superblock layers with different idmaps. Without them, OverlayFS
> uses normal path lookup and keeps the layer chosen during lookup.
>
> Fixes: bc70682a497c ("ovl: support idmapped layers")
> Assisted-by: Codex:gpt-5
> Signed-off-by: Jérémy Jean <Jeremy.Jean@oss.cyber.gouv.fr>

Hi Jeremy,

Nice catch.
Do you have a reproducer which shows privilege escalation?
Please add test cases to selftests/filesystems/overlayfs/idmapped_mounts.c.

> ---
>  fs/overlayfs/super.c | 26 +++++++++++++++++++++++++-
>  1 file changed, 25 insertions(+), 1 deletion(-)
>
> diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c
> index bd0a3f9039d2..00e1b9fccb4f 100644
> --- a/fs/overlayfs/super.c
> +++ b/fs/overlayfs/super.c
> @@ -944,6 +944,22 @@ static bool ovl_lower_uuid_ok(struct ovl_fs *ofs, const uuid_t *uuid)
>         return true;
>  }
>
> +static bool ovl_lower_mnt_idmap_mismatch(struct ovl_fs *ofs,
> +                                        const struct path *path)
> +{
> +       unsigned int i;
> +
> +       for (i = 1; i < ofs->numlayer; i++) {
> +               struct vfsmount *mnt = ofs->layers[i].mnt;
> +
> +               if (mnt->mnt_sb == path->mnt->mnt_sb &&
> +                   mnt_idmap(mnt) != mnt_idmap(path->mnt))
> +                       return true;
> +       }

I don't like introducing this potential quadratic search on all ovl mounts.
I prefer to store a lower_idmap field per ovl_sb, which is set to the idmap
of the first lower layer which is found to have this ovl_sb and then
ovl_lower_mnt_idmap_mismatch() only needs to match it.

> +
> +       return false;
> +}
> +
>  /* Get a unique fsid for the layer */
>  static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path)
>  {
> @@ -956,8 +972,16 @@ static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path)
>         bool warn = false;
>
>         for (i = 0; i < ofs->numfs; i++) {
> -               if (ofs->fs[i].sb == sb)
> +               if (ofs->fs[i].sb == sb) {
> +                       if ((ofs->config.index || ofs->config.nfs_export) &&
> +                           ovl_lower_mnt_idmap_mismatch(ofs, path)) {
> +                               ofs->config.index = false;
> +                               ofs->config.nfs_export = false;
> +                               pr_warn("different idmaps in same lower fs '%pd2', falling back to index=off,nfs_export=off.\n",
> +                                       path->dentry);
> +                       }

I *think* there is no justification to disabling index, only nfs_export.
It matters because some distros enable index by default and not
breaking hardlinks on copy up is not a "feature" it is a correctness issue.

While as you write "both features rely on persistent lower file handles"
I cannot think of any case where index=on alone uses decode of
file handles from the wrong layer leads to bypass permission checks on
lower inode -

The index to lower inode is created only on copy up of a lower inode
that has hardlinks.
This copy up is performed on an overlayfs dentry that was composed by
ovl_lookup() through the correct lower layers.
Copy-up reads uid/gid through the lower dentry's layer idmap and writes them
onto the upper/index inode through the upper layer idmap.
After that, overlay ownership comes from the upper, not from lower inode.

It's probably worth adding some test cases to the idmapped_mounts
selftest to cover copy up.

If the same lower inode is visible from different lower layers
with different idmaps I do not consider that overlayfs job to deal with.
Whichever lower inode is copied up first is the metadata that will be copied
to upper inode and to the overlayfs in-core inode.

TBH, I'm not even sure if there is a legit case for overlayfs mount
of lower layers with same sb and mismatching mntid, but there could be.

CC Christian for his thoughts.

Thanks,
Amir.