ovl: check that the parents of volatile/dirty are dirs

An unprivileged user crashes the kernel by mounting overlayfs with the
volatile option.

A dentry that serves as the parent of a lookup must be a directory.
ovl_create_volatile_dirty() walks work/incompat/volatile/dirty by name,
and ovl_lookup_or_create() returns an existing entry whatever its type.
A file or a symlink that another task creates during the mount becomes
the parent of the next lookup, which calls a NULL ->lookup.

An unprivileged user mounted overlayfs with "volatile" in a user
namespace while a second process created a symlink named work/incompat,
and the mount task oopsed on a NULL ->lookup call.

Fix this by returning -ENOTDIR from ovl_lookup_or_create() when the
caller asked for a directory and the existing entry is a non-dir.

Fixes: c86243b090 ("ovl: provide a mount option "volatile"")
Cc: stable@vger.kernel.org
Signed-off-by: Chris Mason <mason@kernel.org>
Signed-off-by: Josef Bacik <josef@toxicpanda.com>
Signed-off-by: Amir Goldstein <amir73il@gmail.com>
This commit is contained in:
Chris Mason 2026-10-07 10:15:11 +00:00 committed by Amir Goldstein
parent ba227ed05b
commit e34d2ef818

View File

@ -621,16 +621,21 @@ static int ovl_check_rename_whiteout(struct ovl_fs *ofs)
static struct dentry *ovl_lookup_or_create(struct ovl_fs *ofs,
struct dentry *parent,
const char *name, umode_t mode)
const char *name, bool is_dir)
{
struct dentry *child;
child = ovl_start_creating_upper(ofs, parent, &QSTR(name));
if (!IS_ERR(child)) {
if (!child->d_inode)
if (!child->d_inode) {
child = ovl_create_real(ofs, parent, child,
&QSTR(name),
OVL_CATTR(mode));
OVL_CATTR(is_dir ? S_IFDIR :
S_IFREG));
} else if (is_dir && !d_can_lookup(child)) {
end_creating(child);
child = ERR_PTR(-ENOTDIR);
}
end_creating_keep(child);
}
dput(parent);
@ -652,7 +657,7 @@ static int ovl_create_volatile_dirty(struct ovl_fs *ofs)
const char *const *name = volatile_path;
for (ctr = ARRAY_SIZE(volatile_path); ctr; ctr--, name++) {
d = ovl_lookup_or_create(ofs, d, *name, ctr > 1 ? S_IFDIR : S_IFREG);
d = ovl_lookup_or_create(ofs, d, *name, ctr > 1);
if (IS_ERR(d))
return PTR_ERR(d);
}