Skip to content

Commit

Permalink
Merge tag 'ovl-fixes-5.14-rc6-v2' of git://git.kernel.org/pub/scm/lin…
Browse files Browse the repository at this point in the history
…ux/kernel/git/mszeredi/vfs

Pull overlayfs fixes from Miklos Szeredi:
 "Fix several bugs in overlayfs"

* tag 'ovl-fixes-5.14-rc6-v2' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs:
  ovl: prevent private clone if bind mount is not allowed
  ovl: fix uninitialized pointer read in ovl_lookup_real_one()
  ovl: fix deadlock in splice write
  ovl: skip stale entries in merge dir cache iteration
  • Loading branch information
Linus Torvalds committed Aug 10, 2021
2 parents 9a73fa3 + 427215d commit b3f0ccc
Show file tree
Hide file tree
Showing 4 changed files with 80 additions and 16 deletions.
42 changes: 28 additions & 14 deletions fs/namespace.c
Original file line number Diff line number Diff line change
Expand Up @@ -1938,6 +1938,20 @@ void drop_collected_mounts(struct vfsmount *mnt)
namespace_unlock();
}

static bool has_locked_children(struct mount *mnt, struct dentry *dentry)
{
struct mount *child;

list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
if (!is_subdir(child->mnt_mountpoint, dentry))
continue;

if (child->mnt.mnt_flags & MNT_LOCKED)
return true;
}
return false;
}

/**
* clone_private_mount - create a private clone of a path
* @path: path to clone
Expand All @@ -1953,17 +1967,30 @@ struct vfsmount *clone_private_mount(const struct path *path)
struct mount *old_mnt = real_mount(path->mnt);
struct mount *new_mnt;

down_read(&namespace_sem);
if (IS_MNT_UNBINDABLE(old_mnt))
return ERR_PTR(-EINVAL);
goto invalid;

if (!check_mnt(old_mnt))
goto invalid;

if (has_locked_children(old_mnt, path->dentry))
goto invalid;

new_mnt = clone_mnt(old_mnt, path->dentry, CL_PRIVATE);
up_read(&namespace_sem);

if (IS_ERR(new_mnt))
return ERR_CAST(new_mnt);

/* Longterm mount to be removed by kern_unmount*() */
new_mnt->mnt_ns = MNT_NS_INTERNAL;

return &new_mnt->mnt;

invalid:
up_read(&namespace_sem);
return ERR_PTR(-EINVAL);
}
EXPORT_SYMBOL_GPL(clone_private_mount);

Expand Down Expand Up @@ -2315,19 +2342,6 @@ static int do_change_type(struct path *path, int ms_flags)
return err;
}

static bool has_locked_children(struct mount *mnt, struct dentry *dentry)
{
struct mount *child;
list_for_each_entry(child, &mnt->mnt_mounts, mnt_child) {
if (!is_subdir(child->mnt_mountpoint, dentry))
continue;

if (child->mnt.mnt_flags & MNT_LOCKED)
return true;
}
return false;
}

static struct mount *__do_loopback(struct path *old_path, int recurse)
{
struct mount *mnt = ERR_PTR(-EINVAL), *old = real_mount(old_path->mnt);
Expand Down
2 changes: 1 addition & 1 deletion fs/overlayfs/export.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,7 @@ static struct dentry *ovl_lookup_real_one(struct dentry *connected,
*/
take_dentry_name_snapshot(&name, real);
this = lookup_one_len(name.name.name, connected, name.name.len);
release_dentry_name_snapshot(&name);
err = PTR_ERR(this);
if (IS_ERR(this)) {
goto fail;
Expand All @@ -406,7 +407,6 @@ static struct dentry *ovl_lookup_real_one(struct dentry *connected,
}

out:
release_dentry_name_snapshot(&name);
dput(parent);
inode_unlock(dir);
return this;
Expand Down
47 changes: 46 additions & 1 deletion fs/overlayfs/file.c
Original file line number Diff line number Diff line change
Expand Up @@ -392,6 +392,51 @@ static ssize_t ovl_write_iter(struct kiocb *iocb, struct iov_iter *iter)
return ret;
}

/*
* Calling iter_file_splice_write() directly from overlay's f_op may deadlock
* due to lock order inversion between pipe->mutex in iter_file_splice_write()
* and file_start_write(real.file) in ovl_write_iter().
*
* So do everything ovl_write_iter() does and call iter_file_splice_write() on
* the real file.
*/
static ssize_t ovl_splice_write(struct pipe_inode_info *pipe, struct file *out,
loff_t *ppos, size_t len, unsigned int flags)
{
struct fd real;
const struct cred *old_cred;
struct inode *inode = file_inode(out);
struct inode *realinode = ovl_inode_real(inode);
ssize_t ret;

inode_lock(inode);
/* Update mode */
ovl_copyattr(realinode, inode);
ret = file_remove_privs(out);
if (ret)
goto out_unlock;

ret = ovl_real_fdget(out, &real);
if (ret)
goto out_unlock;

old_cred = ovl_override_creds(inode->i_sb);
file_start_write(real.file);

ret = iter_file_splice_write(pipe, real.file, ppos, len, flags);

file_end_write(real.file);
/* Update size */
ovl_copyattr(realinode, inode);
revert_creds(old_cred);
fdput(real);

out_unlock:
inode_unlock(inode);

return ret;
}

static int ovl_fsync(struct file *file, loff_t start, loff_t end, int datasync)
{
struct fd real;
Expand Down Expand Up @@ -603,7 +648,7 @@ const struct file_operations ovl_file_operations = {
.fadvise = ovl_fadvise,
.flush = ovl_flush,
.splice_read = generic_file_splice_read,
.splice_write = iter_file_splice_write,
.splice_write = ovl_splice_write,

.copy_file_range = ovl_copy_file_range,
.remap_file_range = ovl_remap_file_range,
Expand Down
5 changes: 5 additions & 0 deletions fs/overlayfs/readdir.c
Original file line number Diff line number Diff line change
Expand Up @@ -481,6 +481,8 @@ static int ovl_cache_update_ino(struct path *path, struct ovl_cache_entry *p)
}
this = lookup_one_len(p->name, dir, p->len);
if (IS_ERR_OR_NULL(this) || !this->d_inode) {
/* Mark a stale entry */
p->is_whiteout = true;
if (IS_ERR(this)) {
err = PTR_ERR(this);
this = NULL;
Expand Down Expand Up @@ -776,6 +778,9 @@ static int ovl_iterate(struct file *file, struct dir_context *ctx)
if (err)
goto out;
}
}
/* ovl_cache_update_ino() sets is_whiteout on stale entry */
if (!p->is_whiteout) {
if (!dir_emit(ctx, p->name, p->len, p->ino, p->type))
break;
}
Expand Down

0 comments on commit b3f0ccc

Please sign in to comment.