diff --git a/Documentation/filesystems/overlayfs.rst b/Documentation/filesystems/overlayfs.rst index 16c35b491dad..4c241d220bc2 100644 --- a/Documentation/filesystems/overlayfs.rst +++ b/Documentation/filesystems/overlayfs.rst @@ -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 diff --git a/fs/open.c b/fs/open.c index e43f02ff64ac..bb484b07390a 100644 --- a/fs/open.c +++ b/fs/open.c @@ -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 diff --git a/fs/overlayfs/dir.c b/fs/overlayfs/dir.c index 1792e4114621..e0a164e22d67 100644 --- a/fs/overlayfs/dir.c +++ b/fs/overlayfs/dir.c @@ -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); diff --git a/fs/overlayfs/export.c b/fs/overlayfs/export.c index 9aae20c59608..bd998a3d8b95 100644 --- a/fs/overlayfs/export.c +++ b/fs/overlayfs/export.c @@ -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; diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index a11564e90751..f1c6df39d0ae 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -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); diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index c6127edb9be9..64d63b260709 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -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); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index 901335026a20..e42543d4c108 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -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; diff --git a/fs/overlayfs/readdir.c b/fs/overlayfs/readdir.c index 2018e3114655..4cd4af097e5c 100644 --- a/fs/overlayfs/readdir.c +++ b/fs/overlayfs/readdir.c @@ -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); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index d9174eaccce2..8bc970748b96 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -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: diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 521717209b2e..788c6870d67d 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -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); diff --git a/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c b/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c index 0f30a3e6e197..535bf1f4e1a3 100644 --- a/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c +++ b/tools/testing/selftests/filesystems/fscontext_ns/fscontext_ns_test.c @@ -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)