FAILED: patch "[PATCH] ovl: hold a reference on the parent in readdir" failed to apply to 6.6-stable tree
Subsystems (2)
[email protected]
maintainerThe patch below does not apply to the 6.6-stable tree.
If someone wants it applied there, or to any other stable or longterm tree, then please email the backport, including the original git commit id to <[email protected]>.
To reproduce the conflict and resubmit, you may use the following commands:
git fetch https://git.kernel.org/pub/scm/linux/kernel/git/stable/linux.git/ linux-6.6.y
git checkout FETCH_HEAD
git cherry-pick -x 16d319e12328acafeafd3eecdf09c46d7fb43452
# <resolve conflicts, build, test, etc.>
git commit -s
git send-email --to '<[email protected]>' --in-reply-to '2026110447-freestyle-darn-5554@gregkh' --subject-prefix 'PATCH 6.6.y' 'HEAD^..'
Possible dependencies:
thanks,
greg k-h
------------------ original commit in Linus's tree ------------------
From 16d319e12328acafeafd3eecdf09c46d7fb43452 Mon Sep 17 00:00:00 2001
From: Chris Mason <[email protected]>
Date: Wed, 7 Oct 2026 10:53:10 +0000
Subject: [PATCH] ovl: hold a reference on the parent in readdir
Listing a directory on an overlay uses a freed dentry and crashes the kernel when a lookup moves the directory at the same time.
Code that can sleep may only use d_parent without a reference while something stops a move of the dentry. ovl_iterate_real(), ovl_need_adjust_d_ino() and ovl_cache_update() use the d_parent of the listed directory and hold only the lock of that directory. When a lookup finds the directory under another parent after a change to a layer, d_splice_alias() moves it without that lock and drops its reference on the old parent.
With a delay in ovl_iterate_real(), getdents64 on an overlay directory raced a rename in the lower layer and a lookup that moved its dentry, and after an rmdir of the old parent KASAN reported a use-after-free in security_inode_getattr().
Fix this by using dget_parent() in the three functions.
fs/overlayfs/readdir.c +25 -8
@@ -588,8 +588,8 @@ static int ovl_cache_update(const struct path *path, struct ovl_cache_entry *p, goto get; }if (p->len ==2 ) { -/* we shall not be moved */ - this =dget (dir->d_parent); +/* d_splice_alias() can move even a locked dir */ + this =dget_parent (dir); goto get; } }
@@ -818,6 +818,7 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)struct dentry *dir = file->f_path.dentry;struct ovl_fs *ofs =OVL_FS (dir->d_sb);const struct ovl_layer *lower_layer =ovl_layer_lower (dir); +struct dentry *parent;struct ovl_readdir_translate rdt = { .ctx.actor = ovl_fill_real, .ctx.count = ctx->count,
@@ -829,18 +830,26 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)if (rdt.xinobits && lower_layer) rdt.fsid = lower_layer->fsid; -if (OVL_TYPE_MERGE (ovl_path_type (dir->d_parent))) { +/* +* The lock on dir does not stop d_splice_alias() from moving it to +* another parent, which drops the reference dir holds on this one. +*/ + parent =dget_parent (dir); +if (OVL_TYPE_MERGE (ovl_path_type (parent))) {struct kstat stat;struct path statpath = file->f_path; - statpath.dentry = dir->d_parent; + statpath.dentry = parent; err =vfs_getattr (&statpath, &stat,STATX_INO ,0 ); -if (err) +if (err) { +dput (parent);return err; + }WARN_ON_ONCE (dir->d_sb->s_dev != stat.dev); rdt.parent_ino = stat.ino; } +dput (parent);if (ovl_is_impure_dir (file)) { rdt.cache =ovl_cache_get_impure (&file->f_path);
@@ -896,8 +905,10 @@ static bool ovl_need_adjust_d_ino(struct file *file) {struct dentry *dentry = file->f_path.dentry;struct ovl_fs *ofs =OVL_FS (dentry->d_sb); +struct dentry *parent; +enum ovl_path_type type; -/* If parent is merge, then need to adjust d_ino for '..' */ +/* With xino, need to map real d_ino to the unified overlay ino space */ if (ovl_xino_bits (ofs))return true;
@@ -906,8 +917,14 @@ static bool ovl_need_adjust_d_ino(struct file *file)return false;/* If dir is impure then need to adjust d_ino for copied up entries */ -if (ovl_is_impure_dir (file) || -OVL_TYPE_MERGE (ovl_path_type (dentry->d_parent))) +if (ovl_is_impure_dir (file)) +return true; + +/* If parent is merge, then need to adjust d_ino for '..' */ + parent =dget_parent (dentry); + type =ovl_path_type (parent); +dput (parent); +if (OVL_TYPE_MERGE (type))return true;/* Pure: no need to adjust d_ino */
Sasha Levin
maintainerPATCH 1/1 ovl: hold a reference on the parent in readdir
From: Chris Mason <[email protected]>
[ Upstream commit 16d319e12328acafeafd3eecdf09c46d7fb43452 ]
Listing a directory on an overlay uses a freed dentry and crashes the kernel when a lookup moves the directory at the same time.
Code that can sleep may only use d_parent without a reference while something stops a move of the dentry. ovl_iterate_real(), ovl_need_adjust_d_ino() and ovl_cache_update() use the d_parent of the listed directory and hold only the lock of that directory. When a lookup finds the directory under another parent after a change to a layer, d_splice_alias() moves it without that lock and drops its reference on the old parent.
With a delay in ovl_iterate_real(), getdents64 on an overlay directory raced a rename in the lower layer and a lookup that moved its dentry, and after an rmdir of the old parent KASAN reported a use-after-free in security_inode_getattr().
Fix this by using dget_parent() in the three functions.
--- fs/overlayfs/readdir.c | 24 ++++++++++++++++++------ 1 file changed, 18 insertions(+), 6 deletions(-)
fs/overlayfs/readdir.c +18 -6
@@ -476,8 +476,8 @@ static int ovl_cache_update_ino(const struct path *path, struct ovl_cache_entry goto get; }if (p->len ==2 && p->name[1 ] =='.' ) { -/* we shall not be moved */ - this =dget (dir->d_parent); +/* d_splice_alias() can move even a locked dir */ + this =dget_parent (dir); goto get; } }
@@ -697,6 +697,7 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)struct dentry *dir = file->f_path.dentry;struct ovl_fs *ofs =OVL_FS (dir->d_sb);const struct ovl_layer *lower_layer =ovl_layer_lower (dir); +struct dentry *parent;struct ovl_readdir_translate rdt = { .ctx.actor = ovl_fill_real, .orig_ctx = ctx,
@@ -707,18 +708,26 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)if (rdt.xinobits && lower_layer) rdt.fsid = lower_layer->fsid; -if (OVL_TYPE_MERGE (ovl_path_type (dir->d_parent))) { +/* +* The lock on dir does not stop d_splice_alias() from moving it to +* another parent, which drops the reference dir holds on this one. +*/ + parent =dget_parent (dir); +if (OVL_TYPE_MERGE (ovl_path_type (parent))) {struct kstat stat;struct path statpath = file->f_path; - statpath.dentry = dir->d_parent; + statpath.dentry = parent; err =vfs_getattr (&statpath, &stat,STATX_INO ,0 ); -if (err) +if (err) { +dput (parent);return err; + }WARN_ON_ONCE (dir->d_sb->s_dev != stat.dev); rdt.parent_ino = stat.ino; } +dput (parent);if (ovl_is_impure_dir (file)) { rdt.cache =ovl_cache_get_impure (&file->f_path);
@@ -747,6 +756,8 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)ovl_dir_reset (file);if (od->is_real) { +struct dentry *parent =dget_parent (dentry); +/* * If parent is merge, then need to adjust d_ino for '..', if * dir is impure then need to adjust d_ino for copied up
@@ -755,11 +766,12 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)if (ovl_xino_bits (ofs) || (ovl_same_fs (ofs) && (ovl_is_impure_dir (file) || -OVL_TYPE_MERGE (ovl_path_type (dentry->d_parent))))) { +OVL_TYPE_MERGE (ovl_path_type (parent))))) { err =ovl_iterate_real (file, ctx); }else { err =iterate_dir (od->realfile, ctx); } +dput (parent); goto out; }
Signature · 1 line
2.53.0