overlayfs fixes for 7.4-rc2

-----BEGIN PGP SIGNATURE-----
 
 iQIzBAABCAAdFiEE9zuTYTs0RXF+Ke33EVvVyTe/1WoFAmrsdYYACgkQEVvVyTe/
 1Wpjzg/+Lr21Tf5jrjrRE9Pgim8LMSqKwR0GV9WnYmHGMrQ6bk5vG0O5QnwFMZnf
 M4lxDvzyoYmaW2U7EILRz9YKAjoUT/5XRf40NN9OMFy/1mvKJCCk5zm7uxOGixf5
 nQW86p/ihzsIMxPGmKEAhFONAme92dLbyK355eCcOCFVryaFryrEkemicncMZMPt
 TO/+/Xm/f8ydxSzx0MjBHcM93Po/GwRYdvsRtG7H/Ov+4PoqtS0PTnMr1/lip6lA
 M+kX2ocG+GRjokQ1Jm8fIO437ZCZLDjNgi5lpUa+tmuQDsIfCTYdCk7a1hjgDlsb
 vENrQH0vf72ZM6IzayKnmJO3MKLvGRVj16WdS+3JTrnPu9BicjpRSfk/Koy1b7eS
 T/ahZPxoGBKPTKBdeLLddDGsSNfVtrRIys35oNjPC9Po83Su3FHrO7qp8iYf9caN
 2G5YFzvowC7pOsRMVpwuQzJYC56H+ID2mcyORUmJaQqIW+g/zXCaMcjZeGDDHb4i
 ipvXtcbRD73DOAS1cb4SOYMMK3XtAWSpB/dOttV9mw4rpDhuS3HwCDGVqbyozfGT
 ePOE7UQJZZ15AH3FtdUeuXT0DI/fkB4Z4U0kyfoQIPGdDuakbLWNl6GpQm8b0B8K
 JgIHY35lx/j4rM/NrB325jb1zknDjl381wHzHT2/b9eaFzorvIE=
 =XTA5
 -----END PGP SIGNATURE-----

Merge tag 'ovl-fixes-7.4-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs

Pull overlayfs fixes from Amir Goldstein:

 - Fix a self deadlock of a fanotify permission event listener that also
   watches the layers, when it reads an overlay directory

 - Three casefold fixes, found by Chris Mason's AI review

 - Fix a decode of a modified file handle with nfs_export=on, which
   could read the stale lower data of a copied up file, also found by
   Chris Mason's AI review

 - Fix an overlay selftest after an errno change

* tag 'ovl-fixes-7.4-rc2' of git://git.kernel.org/pub/scm/linux/kernel/git/overlayfs/vfs:
  selftests/fscontext_ns: expect EINVAL for overlay descendant userns create
  ovl: look up the index by the decoded origin
  ovl: inherit FMODE_NONOTIFY when reading directory layers
  ovl: inherit FMODE_NONOTIFY when opening directory realfiles
  ovl: fix whiteout creation in casefolded directories
  ovl: do not let chattr change the casefold flag
  ovl: fail the mount if the casefold flag has changed
This commit is contained in:
Linus Torvalds 2026-11-06 08:28:14 -08:00
commit ce8f728086
11 changed files with 102 additions and 56 deletions

View File

@ -731,7 +731,8 @@ When decoding an overlay file handle, the following steps are followed:
1. Find underlying layer by UUID and path type information.
2. Decode the underlying filesystem file handle to underlying dentry.
3. For a lower file handle, lookup the handle in index directory by name.
3. For a lower file handle, encode the decoded underlying inode again and
lookup that handle in index directory by name.
4. If a whiteout is found in index, return ESTALE. This represents an
overlay object that was deleted after its file handle was encoded.
5. For a non-directory, instantiate a disconnected overlay dentry from the

View File

@ -1172,6 +1172,7 @@ struct file *dentry_open_nonotify(const struct path *path, int flags,
}
return f;
}
EXPORT_SYMBOL_GPL(dentry_open_nonotify);
/**
* kernel_file_open - open a file for kernel internal use

View File

@ -74,39 +74,34 @@ static struct dentry *ovl_start_creating_temp(struct ovl_fs *ofs,
&QSTR(name));
}
/*
* The whiteout is the source of a rename, so it has to be hashed.
* ovl_create_temp() takes care of that for a casefolded workdir.
*/
static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
{
int err;
struct dentry *whiteout, *link;
struct dentry *workdir = ofs->workdir;
struct inode *wdir = workdir->d_inode;
char name[OVL_TEMPNAME_SIZE];
guard(mutex)(&ofs->whiteout_lock);
if (!ofs->whiteout) {
whiteout = ovl_start_creating_temp(ofs, workdir, name);
whiteout = ovl_create_temp(ofs, workdir, &(struct ovl_cattr) {
.mode = S_IFCHR | WHITEOUT_MODE,
.rdev = WHITEOUT_DEV });
if (IS_ERR(whiteout))
return whiteout;
err = ovl_do_whiteout(ofs, wdir, whiteout);
if (!err)
ofs->whiteout = dget(whiteout);
end_creating(whiteout);
if (err)
return ERR_PTR(err);
ofs->whiteout = whiteout;
}
if (!ofs->no_shared_whiteout) {
link = ovl_start_creating_temp(ofs, workdir, name);
if (IS_ERR(link))
link = ovl_create_temp(ofs, workdir, &(struct ovl_cattr) {
.hardlink = ofs->whiteout });
if (!IS_ERR(link))
return link;
err = ovl_do_link(ofs, ofs->whiteout, wdir, link);
if (!err)
whiteout = dget(link);
end_creating(link);
if (!err)
return whiteout;
err = PTR_ERR(link);
if (err != -EMLINK) {
pr_warn("Failed to link whiteout - disabling whiteout inode sharing(nlink=%u, err=%u)\n",
ofs->whiteout->d_inode->i_nlink,
@ -120,9 +115,10 @@ static struct dentry *ovl_whiteout(struct ovl_fs *ofs)
}
int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
struct dentry *dentry)
struct dentry *dentry, const struct qstr *name)
{
struct dentry *whiteout;
struct qstr last = QSTR_LEN(name->name, name->len);
struct renamedata rd = {};
int err;
int flags = 0;
@ -139,9 +135,17 @@ int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
rd.old_parent = ofs->workdir;
rd.new_parent = dir;
rd.flags = flags;
err = start_renaming_two_dentries(&rd, whiteout, dentry);
/*
* The caller looked up @dentry by @name without a lock on @dir, and a
* casefolded directory leaves a negative dentry unhashed. Nothing tells
* if such a dentry is still valid, so look up @name again with @dir locked.
*/
err = start_renaming_dentry(&rd, 0, whiteout, &last);
if (!err) {
err = ovl_do_rename_rd(&rd);
if (d_inode(rd.new_dentry) != d_inode(dentry))
err = -ESTALE;
else
err = ovl_do_rename_rd(&rd);
end_renaming(&rd);
}
if (err)
@ -847,7 +851,7 @@ static int ovl_remove_and_whiteout(struct dentry *dentry,
goto out_dput_upper;
}
err = ovl_cleanup_and_whiteout(ofs, upperdir, upper);
err = ovl_cleanup_and_whiteout(ofs, upperdir, upper, &dentry->d_name);
if (!err)
ovl_dir_modified(dentry->d_parent, true);

View File

@ -713,9 +713,9 @@ static struct dentry *ovl_lower_fh_to_d(struct super_block *sb,
}
}
/* Then lookup indexed upper/whiteout by origin fh */
/* Then lookup indexed upper/whiteout by origin */
if (ovl_indexdir(sb)) {
index = ovl_get_index_fh(ofs, fh);
index = ovl_get_index_origin(ofs, origin.dentry);
err = PTR_ERR(index);
if (IS_ERR(index)) {
index = NULL;

View File

@ -658,6 +658,14 @@ int ovl_fileattr_set(const struct mnt_idmap *idmap,
unsigned int flags;
int err;
/*
* Lookup refuses a directory that does not agree with the casefold
* state of the mount. Only FS_IOC_SETFLAGS can ask for a change.
*/
if (S_ISDIR(inode->i_mode) && fa->flags_valid &&
!!(fa->flags & FS_CASEFOLD_FL) != OVL_FS(inode->i_sb)->casefold)
return -EOPNOTSUPP;
err = ovl_copy_up(dentry);
if (!err) {
ovl_path_real(dentry, &upperpath);

View File

@ -781,14 +781,21 @@ int ovl_get_index_name(struct ovl_fs *ofs, struct dentry *origin,
return err;
}
/* Lookup index by file handle for NFS export */
struct dentry *ovl_get_index_fh(struct ovl_fs *ofs, struct ovl_fh *fh)
/*
* Lookup index by decoded origin for NFS export.
*
* The name of an index is the hex of the file handle that copy up encoded.
* The bytes of the handle to decode cannot be used as the name, because a
* lower fs may decode many handles to the same inode. For example, it may
* ignore the fid type, extra fid bytes or a zero generation.
*/
struct dentry *ovl_get_index_origin(struct ovl_fs *ofs, struct dentry *origin)
{
struct dentry *index;
struct qstr name;
int err;
err = ovl_get_index_name_fh(fh, &name);
err = ovl_get_index_name(ofs, origin, &name);
if (err)
return ERR_PTR(err);

View File

@ -396,14 +396,6 @@ static inline int ovl_do_rename(struct ovl_fs *ofs, struct dentry *olddir,
return ovl_do_rename_rd(&rd);
}
static inline int ovl_do_whiteout(struct ovl_fs *ofs,
struct inode *dir, struct dentry *dentry)
{
int err = vfs_whiteout(ovl_upper_mnt_idmap(ofs), dir, dentry);
pr_debug("whiteout(%pd2) = %i\n", dentry, err);
return err;
}
static inline struct file *ovl_do_tmpfile(struct ovl_fs *ofs,
struct dentry *dentry, umode_t mode)
{
@ -539,7 +531,14 @@ void ovl_dir_modified(struct dentry *dentry, bool impurity);
u64 ovl_inode_version_get(struct inode *inode);
bool ovl_is_whiteout(struct dentry *dentry);
bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path);
struct file *ovl_path_open(const struct path *path, int flags);
struct file *ovl_path_open_realfile(const struct file *user_file,
const struct path *path, int flags);
static inline struct file *ovl_path_open(const struct path *path, int flags)
{
return ovl_path_open_realfile(NULL, path, flags);
}
int ovl_copy_up_start(struct dentry *dentry, int flags);
void ovl_copy_up_end(struct dentry *dentry);
bool ovl_already_copied_up(struct dentry *dentry, int flags);
@ -751,7 +750,7 @@ int ovl_verify_index(struct ovl_fs *ofs, struct dentry *index);
int ovl_get_index_name_fh(const struct ovl_fh *fh, struct qstr *name);
int ovl_get_index_name(struct ovl_fs *ofs, struct dentry *origin,
struct qstr *name);
struct dentry *ovl_get_index_fh(struct ovl_fs *ofs, struct ovl_fh *fh);
struct dentry *ovl_get_index_origin(struct ovl_fs *ofs, struct dentry *origin);
struct dentry *ovl_lookup_index(struct ovl_fs *ofs, struct dentry *upper,
struct dentry *origin, bool verify);
int ovl_path_next(int idx, struct dentry *dentry, struct path *path,
@ -899,7 +898,7 @@ static inline void ovl_copyflags(struct inode *from, struct inode *to)
/* dir.c */
extern const struct inode_operations ovl_dir_inode_operations;
int ovl_cleanup_and_whiteout(struct ovl_fs *ofs, struct dentry *dir,
struct dentry *dentry);
struct dentry *dentry, const struct qstr *name);
struct ovl_cattr {
dev_t rdev;
umode_t mode;

View File

@ -43,6 +43,7 @@ struct ovl_dir_cache {
struct ovl_readdir_data {
struct dir_context ctx;
const struct file *user_file;
struct dentry *dentry;
bool is_lowest;
struct rb_root *root;
@ -377,7 +378,8 @@ static inline int ovl_dir_read(const struct path *realpath,
struct file *realfile;
int err;
realfile = ovl_path_open(realpath, O_RDONLY | O_LARGEFILE);
realfile = ovl_path_open_realfile(rdd->user_file, realpath,
O_RDONLY | O_LARGEFILE);
if (IS_ERR(realfile))
return PTR_ERR(realfile);
@ -421,13 +423,14 @@ static void ovl_dir_reset(struct file *file)
}
static int ovl_dir_read_merged(struct dentry *dentry, struct list_head *list,
struct rb_root *root)
struct rb_root *root, const struct file *user_file)
{
int err;
struct path realpath;
struct ovl_readdir_data rdd = {
.ctx.actor = ovl_fill_merge,
.ctx.count = INT_MAX,
.user_file = user_file,
.dentry = dentry,
.list = list,
.root = root,
@ -498,9 +501,10 @@ static struct ovl_dir_cache *ovl_dir_cache_check(struct inode *inode,
return cache;
}
static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
static struct ovl_dir_cache *ovl_dir_cache_get(struct file *file)
{
int res;
struct dentry *dentry = file->f_path.dentry;
struct ovl_dir_cache *cache;
struct inode *inode = d_inode(dentry);
@ -521,7 +525,7 @@ static struct ovl_dir_cache *ovl_cache_get(struct dentry *dentry)
INIT_LIST_HEAD(&cache->entries);
cache->root = RB_ROOT;
res = ovl_dir_read_merged(dentry, &cache->entries, &cache->root);
res = ovl_dir_read_merged(dentry, &cache->entries, &cache->root, file);
if (res) {
ovl_cache_free(&cache->entries);
kfree(cache);
@ -665,8 +669,8 @@ static bool ovl_fill_plain(struct dir_context *ctx, const char *name,
return true;
}
static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
struct rb_root *root)
static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
struct rb_root *root, const struct file *user_file)
{
int err;
struct path realpath;
@ -674,6 +678,7 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
struct ovl_readdir_data rdd = {
.ctx.actor = ovl_fill_plain,
.ctx.count = INT_MAX,
.user_file = user_file,
.list = list,
.root = root,
};
@ -710,9 +715,10 @@ static int ovl_dir_read_impure(const struct path *path, struct list_head *list,
return 0;
}
static struct ovl_dir_cache *ovl_cache_get_impure(const struct path *path)
static struct ovl_dir_cache *ovl_cache_get_impure(struct file *file)
{
int res;
const struct path *path = &file->f_path;
struct dentry *dentry = path->dentry;
struct inode *inode = d_inode(dentry);
struct ovl_fs *ofs = OVL_FS(dentry->d_sb);
@ -731,7 +737,7 @@ static struct ovl_dir_cache *ovl_cache_get_impure(const struct path *path)
return ERR_PTR(-ENOMEM);
cache->is_refcounted = false;
res = ovl_dir_read_impure(path, &cache->entries, &cache->root);
res = ovl_dir_read_impure(path, &cache->entries, &cache->root, file);
if (res) {
ovl_cache_free(&cache->entries);
kfree(cache);
@ -852,7 +858,7 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
dput(parent);
if (ovl_is_impure_dir(file)) {
rdt.cache = ovl_cache_get_impure(&file->f_path);
rdt.cache = ovl_cache_get_impure(file);
if (IS_ERR(rdt.cache))
return PTR_ERR(rdt.cache);
}
@ -866,14 +872,13 @@ static int ovl_iterate_real(struct file *file, struct dir_context *ctx)
static int ovl_iterate_merged(struct file *file, struct dir_context *ctx)
{
struct ovl_dir_file *od = file->private_data;
struct dentry *dentry = file->f_path.dentry;
struct ovl_cache_entry *p;
int err;
if (!od->cache) {
struct ovl_dir_cache *cache;
cache = ovl_cache_get(dentry);
cache = ovl_dir_cache_get(file);
if (IS_ERR(cache))
return PTR_ERR(cache);
@ -994,7 +999,8 @@ static struct file *ovl_dir_open_realfile(const struct file *file,
const struct path *realpath)
{
with_ovl_creds(file_inode(file)->i_sb)
return ovl_path_open(realpath, O_RDONLY | (file->f_flags & O_LARGEFILE));
return ovl_path_open_realfile(file, realpath,
O_RDONLY | (file->f_flags & O_LARGEFILE));
}
/*
@ -1119,7 +1125,7 @@ int ovl_check_empty_dir(struct dentry *dentry, struct list_head *list)
struct rb_root root = RB_ROOT;
with_ovl_creds(dentry->d_sb)
err = ovl_dir_read_merged(dentry, list, &root);
err = ovl_dir_read_merged(dentry, list, &root, NULL);
if (err)
return err;
@ -1343,7 +1349,8 @@ int ovl_indexdir_cleanup(struct ovl_fs *ofs)
* Whiteout orphan index to block future open by
* handle after overlay nlink dropped to zero.
*/
err = ovl_cleanup_and_whiteout(ofs, indexdir, index);
err = ovl_cleanup_and_whiteout(ofs, indexdir, index,
&QSTR_LEN(p->name, p->len));
} else {
/* Cleanup orphan index entries */
err = ovl_cleanup(ofs, indexdir, index);

View File

@ -1387,7 +1387,6 @@ static struct dentry *ovl_get_root(struct super_block *sb,
ovl_dentry_set_flag(OVL_E_CONNECTED, root);
ovl_set_upperdata(d_inode(root));
ovl_inode_init(d_inode(root), &oip, ino, fsid);
WARN_ON(!!IS_CASEFOLDED(d_inode(root)) != ofs->casefold);
ovl_dentry_init_flags(root, upperdentry, oe, DCACHE_OP_WEAK_REVALIDATE);
/* root keeps a reference of upperdentry */
dget(upperdentry);
@ -1571,6 +1570,17 @@ static int ovl_fill_super_creds(struct fs_context *fc, struct super_block *sb)
if (!sb->s_root)
goto out_free_oe;
/*
* The casefold flag of an empty layer root may have changed since the
* mount options were parsed. The root inode owns oe by now.
*/
if (!!IS_CASEFOLDED(d_inode(sb->s_root)) != ofs->casefold) {
pr_err("casefold flag of the root directory has changed\n");
dput(sb->s_root);
sb->s_root = NULL;
return -EINVAL;
}
return 0;
out_free_oe:

View File

@ -654,7 +654,8 @@ bool ovl_path_is_whiteout(struct ovl_fs *ofs, const struct path *path)
ovl_path_check_xwhiteout_xattr(ofs, path);
}
struct file *ovl_path_open(const struct path *path, int flags)
struct file *ovl_path_open_realfile(const struct file *user_file,
const struct path *path, int flags)
{
struct inode *inode = d_inode(path->dentry);
const struct mnt_idmap *real_idmap = mnt_idmap(path->mnt);
@ -682,6 +683,14 @@ struct file *ovl_path_open(const struct path *path, int flags)
if (inode_owner_or_capable(real_idmap, inode))
flags |= O_NOATIME;
/*
* Directory opens use dentry_open() here, not backing_file_open().
* Inherit FMODE_NONOTIFY from a fanotify permission fd so a listener
* that also watches the real filesystem does not deadlock.
*/
if (user_file && FMODE_FSNOTIFY_NONE(user_file->f_mode))
return dentry_open_nonotify(path, flags, current_cred());
return dentry_open(path, flags, current_cred());
}
@ -1118,7 +1127,7 @@ static void ovl_cleanup_index(struct dentry *dentry)
} else if (ovl_index_all(dentry->d_sb)) {
/* Whiteout orphan index to block future open by handle */
err = ovl_cleanup_and_whiteout(OVL_FS(dentry->d_sb),
indexdir, index);
indexdir, index, &name);
} else {
/* Cleanup orphan index entries */
err = ovl_cleanup(ofs, indexdir, index);

View File

@ -193,7 +193,7 @@ FIXTURE_VARIANT_ADD(fscontext_ns, binfmt_misc) {
FIXTURE_VARIANT_ADD(fscontext_ns, overlay) {
.fsname = "overlay",
.expected_errno = EIO,
.expected_errno = EINVAL,
};
TEST_F(fscontext_ns, create_from_descendant_userns)