Merge branch 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs
Pull overlayfs fixes from Miklos Szeredi: "Fix a regression in 4.14 and one in 4.13. The latter is a case when Docker is doing something it really shouldn't and gets away with it. We now print a warning instead of erroring out. There are also fixes to several error paths" * 'overlayfs-linus' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: ovl: fix regression caused by exclusive upper/work dir protection ovl: fix missing unlock_rename() in ovl_do_copy_up() ovl: fix dentry leak in ovl_indexdir_cleanup() ovl: fix dput() of ERR_PTR in ovl_cleanup_index() ovl: fix error value printed in ovl_lookup_index() ovl: fix may_write_real() for overlayfs directories
This commit is contained in:
commit
8d4ef4e15e
|
@ -210,8 +210,11 @@ path as another overlay mount and it may use a lower layer path that is
|
||||||
beneath or above the path of another overlay lower layer path.
|
beneath or above the path of another overlay lower layer path.
|
||||||
|
|
||||||
Using an upper layer path and/or a workdir path that are already used by
|
Using an upper layer path and/or a workdir path that are already used by
|
||||||
another overlay mount is not allowed and will fail with EBUSY. Using
|
another overlay mount is not allowed and may fail with EBUSY. Using
|
||||||
partially overlapping paths is not allowed but will not fail with EBUSY.
|
partially overlapping paths is not allowed but will not fail with EBUSY.
|
||||||
|
If files are accessed from two overlayfs mounts which share or overlap the
|
||||||
|
upper layer and/or workdir path the behavior of the overlay is undefined,
|
||||||
|
though it will not result in a crash or deadlock.
|
||||||
|
|
||||||
Mounting an overlay using an upper layer path, where the upper layer path
|
Mounting an overlay using an upper layer path, where the upper layer path
|
||||||
was previously used by another mounted overlay in combination with a
|
was previously used by another mounted overlay in combination with a
|
||||||
|
|
|
@ -468,7 +468,9 @@ static inline int may_write_real(struct file *file)
|
||||||
|
|
||||||
/* File refers to upper, writable layer? */
|
/* File refers to upper, writable layer? */
|
||||||
upperdentry = d_real(dentry, NULL, 0, D_REAL_UPPER);
|
upperdentry = d_real(dentry, NULL, 0, D_REAL_UPPER);
|
||||||
if (upperdentry && file_inode(file) == d_inode(upperdentry))
|
if (upperdentry &&
|
||||||
|
(file_inode(file) == d_inode(upperdentry) ||
|
||||||
|
file_inode(file) == d_inode(dentry)))
|
||||||
return 0;
|
return 0;
|
||||||
|
|
||||||
/* Lower layer: can't write to real file, sorry... */
|
/* Lower layer: can't write to real file, sorry... */
|
||||||
|
|
|
@ -561,10 +561,8 @@ static int ovl_do_copy_up(struct ovl_copy_up_ctx *c)
|
||||||
c->tmpfile = true;
|
c->tmpfile = true;
|
||||||
err = ovl_copy_up_locked(c);
|
err = ovl_copy_up_locked(c);
|
||||||
} else {
|
} else {
|
||||||
err = -EIO;
|
err = ovl_lock_rename_workdir(c->workdir, c->destdir);
|
||||||
if (lock_rename(c->workdir, c->destdir) != NULL) {
|
if (!err) {
|
||||||
pr_err("overlayfs: failed to lock workdir+upperdir\n");
|
|
||||||
} else {
|
|
||||||
err = ovl_copy_up_locked(c);
|
err = ovl_copy_up_locked(c);
|
||||||
unlock_rename(c->workdir, c->destdir);
|
unlock_rename(c->workdir, c->destdir);
|
||||||
}
|
}
|
||||||
|
|
|
@ -216,26 +216,6 @@ out_unlock:
|
||||||
return err;
|
return err;
|
||||||
}
|
}
|
||||||
|
|
||||||
static int ovl_lock_rename_workdir(struct dentry *workdir,
|
|
||||||
struct dentry *upperdir)
|
|
||||||
{
|
|
||||||
/* Workdir should not be the same as upperdir */
|
|
||||||
if (workdir == upperdir)
|
|
||||||
goto err;
|
|
||||||
|
|
||||||
/* Workdir should not be subdir of upperdir and vice versa */
|
|
||||||
if (lock_rename(workdir, upperdir) != NULL)
|
|
||||||
goto err_unlock;
|
|
||||||
|
|
||||||
return 0;
|
|
||||||
|
|
||||||
err_unlock:
|
|
||||||
unlock_rename(workdir, upperdir);
|
|
||||||
err:
|
|
||||||
pr_err("overlayfs: failed to lock workdir+upperdir\n");
|
|
||||||
return -EIO;
|
|
||||||
}
|
|
||||||
|
|
||||||
static struct dentry *ovl_clear_empty(struct dentry *dentry,
|
static struct dentry *ovl_clear_empty(struct dentry *dentry,
|
||||||
struct list_head *list)
|
struct list_head *list)
|
||||||
{
|
{
|
||||||
|
|
|
@ -506,6 +506,7 @@ static struct dentry *ovl_lookup_index(struct dentry *dentry,
|
||||||
|
|
||||||
index = lookup_one_len_unlocked(name.name, ofs->indexdir, name.len);
|
index = lookup_one_len_unlocked(name.name, ofs->indexdir, name.len);
|
||||||
if (IS_ERR(index)) {
|
if (IS_ERR(index)) {
|
||||||
|
err = PTR_ERR(index);
|
||||||
pr_warn_ratelimited("overlayfs: failed inode index lookup (ino=%lu, key=%*s, err=%i);\n"
|
pr_warn_ratelimited("overlayfs: failed inode index lookup (ino=%lu, key=%*s, err=%i);\n"
|
||||||
"overlayfs: mount with '-o index=off' to disable inodes index.\n",
|
"overlayfs: mount with '-o index=off' to disable inodes index.\n",
|
||||||
d_inode(origin)->i_ino, name.len, name.name,
|
d_inode(origin)->i_ino, name.len, name.name,
|
||||||
|
|
|
@ -235,6 +235,7 @@ bool ovl_inuse_trylock(struct dentry *dentry);
|
||||||
void ovl_inuse_unlock(struct dentry *dentry);
|
void ovl_inuse_unlock(struct dentry *dentry);
|
||||||
int ovl_nlink_start(struct dentry *dentry, bool *locked);
|
int ovl_nlink_start(struct dentry *dentry, bool *locked);
|
||||||
void ovl_nlink_end(struct dentry *dentry, bool locked);
|
void ovl_nlink_end(struct dentry *dentry, bool locked);
|
||||||
|
int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir);
|
||||||
|
|
||||||
static inline bool ovl_is_impuredir(struct dentry *dentry)
|
static inline bool ovl_is_impuredir(struct dentry *dentry)
|
||||||
{
|
{
|
||||||
|
|
|
@ -37,6 +37,9 @@ struct ovl_fs {
|
||||||
bool noxattr;
|
bool noxattr;
|
||||||
/* sb common to all layers */
|
/* sb common to all layers */
|
||||||
struct super_block *same_sb;
|
struct super_block *same_sb;
|
||||||
|
/* Did we take the inuse lock? */
|
||||||
|
bool upperdir_locked;
|
||||||
|
bool workdir_locked;
|
||||||
};
|
};
|
||||||
|
|
||||||
/* private information held for every overlayfs dentry */
|
/* private information held for every overlayfs dentry */
|
||||||
|
|
|
@ -988,6 +988,7 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,
|
||||||
struct path *lowerstack, unsigned int numlower)
|
struct path *lowerstack, unsigned int numlower)
|
||||||
{
|
{
|
||||||
int err;
|
int err;
|
||||||
|
struct dentry *index = NULL;
|
||||||
struct inode *dir = dentry->d_inode;
|
struct inode *dir = dentry->d_inode;
|
||||||
struct path path = { .mnt = mnt, .dentry = dentry };
|
struct path path = { .mnt = mnt, .dentry = dentry };
|
||||||
LIST_HEAD(list);
|
LIST_HEAD(list);
|
||||||
|
@ -1007,8 +1008,6 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,
|
||||||
|
|
||||||
inode_lock_nested(dir, I_MUTEX_PARENT);
|
inode_lock_nested(dir, I_MUTEX_PARENT);
|
||||||
list_for_each_entry(p, &list, l_node) {
|
list_for_each_entry(p, &list, l_node) {
|
||||||
struct dentry *index;
|
|
||||||
|
|
||||||
if (p->name[0] == '.') {
|
if (p->name[0] == '.') {
|
||||||
if (p->len == 1)
|
if (p->len == 1)
|
||||||
continue;
|
continue;
|
||||||
|
@ -1018,6 +1017,7 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,
|
||||||
index = lookup_one_len(p->name, dentry, p->len);
|
index = lookup_one_len(p->name, dentry, p->len);
|
||||||
if (IS_ERR(index)) {
|
if (IS_ERR(index)) {
|
||||||
err = PTR_ERR(index);
|
err = PTR_ERR(index);
|
||||||
|
index = NULL;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
err = ovl_verify_index(index, lowerstack, numlower);
|
err = ovl_verify_index(index, lowerstack, numlower);
|
||||||
|
@ -1029,7 +1029,9 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt,
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
dput(index);
|
dput(index);
|
||||||
|
index = NULL;
|
||||||
}
|
}
|
||||||
|
dput(index);
|
||||||
inode_unlock(dir);
|
inode_unlock(dir);
|
||||||
out:
|
out:
|
||||||
ovl_cache_free(&list);
|
ovl_cache_free(&list);
|
||||||
|
|
|
@ -211,9 +211,10 @@ static void ovl_put_super(struct super_block *sb)
|
||||||
|
|
||||||
dput(ufs->indexdir);
|
dput(ufs->indexdir);
|
||||||
dput(ufs->workdir);
|
dput(ufs->workdir);
|
||||||
|
if (ufs->workdir_locked)
|
||||||
ovl_inuse_unlock(ufs->workbasedir);
|
ovl_inuse_unlock(ufs->workbasedir);
|
||||||
dput(ufs->workbasedir);
|
dput(ufs->workbasedir);
|
||||||
if (ufs->upper_mnt)
|
if (ufs->upper_mnt && ufs->upperdir_locked)
|
||||||
ovl_inuse_unlock(ufs->upper_mnt->mnt_root);
|
ovl_inuse_unlock(ufs->upper_mnt->mnt_root);
|
||||||
mntput(ufs->upper_mnt);
|
mntput(ufs->upper_mnt);
|
||||||
for (i = 0; i < ufs->numlower; i++)
|
for (i = 0; i < ufs->numlower; i++)
|
||||||
|
@ -881,9 +882,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
||||||
goto out_put_upperpath;
|
goto out_put_upperpath;
|
||||||
|
|
||||||
err = -EBUSY;
|
err = -EBUSY;
|
||||||
if (!ovl_inuse_trylock(upperpath.dentry)) {
|
if (ovl_inuse_trylock(upperpath.dentry)) {
|
||||||
pr_err("overlayfs: upperdir is in-use by another mount\n");
|
ufs->upperdir_locked = true;
|
||||||
|
} else if (ufs->config.index) {
|
||||||
|
pr_err("overlayfs: upperdir is in-use by another mount, mount with '-o index=off' to override exclusive upperdir protection.\n");
|
||||||
goto out_put_upperpath;
|
goto out_put_upperpath;
|
||||||
|
} else {
|
||||||
|
pr_warn("overlayfs: upperdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
err = ovl_mount_dir(ufs->config.workdir, &workpath);
|
err = ovl_mount_dir(ufs->config.workdir, &workpath);
|
||||||
|
@ -901,9 +906,13 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = -EBUSY;
|
err = -EBUSY;
|
||||||
if (!ovl_inuse_trylock(workpath.dentry)) {
|
if (ovl_inuse_trylock(workpath.dentry)) {
|
||||||
pr_err("overlayfs: workdir is in-use by another mount\n");
|
ufs->workdir_locked = true;
|
||||||
|
} else if (ufs->config.index) {
|
||||||
|
pr_err("overlayfs: workdir is in-use by another mount, mount with '-o index=off' to override exclusive workdir protection.\n");
|
||||||
goto out_put_workpath;
|
goto out_put_workpath;
|
||||||
|
} else {
|
||||||
|
pr_warn("overlayfs: workdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
ufs->workbasedir = workpath.dentry;
|
ufs->workbasedir = workpath.dentry;
|
||||||
|
@ -1156,10 +1165,12 @@ out_put_lowerpath:
|
||||||
out_free_lowertmp:
|
out_free_lowertmp:
|
||||||
kfree(lowertmp);
|
kfree(lowertmp);
|
||||||
out_unlock_workdentry:
|
out_unlock_workdentry:
|
||||||
|
if (ufs->workdir_locked)
|
||||||
ovl_inuse_unlock(workpath.dentry);
|
ovl_inuse_unlock(workpath.dentry);
|
||||||
out_put_workpath:
|
out_put_workpath:
|
||||||
path_put(&workpath);
|
path_put(&workpath);
|
||||||
out_unlock_upperdentry:
|
out_unlock_upperdentry:
|
||||||
|
if (ufs->upperdir_locked)
|
||||||
ovl_inuse_unlock(upperpath.dentry);
|
ovl_inuse_unlock(upperpath.dentry);
|
||||||
out_put_upperpath:
|
out_put_upperpath:
|
||||||
path_put(&upperpath);
|
path_put(&upperpath);
|
||||||
|
|
|
@ -430,7 +430,7 @@ void ovl_inuse_unlock(struct dentry *dentry)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* Called must hold OVL_I(inode)->oi_lock */
|
/* Caller must hold OVL_I(inode)->lock */
|
||||||
static void ovl_cleanup_index(struct dentry *dentry)
|
static void ovl_cleanup_index(struct dentry *dentry)
|
||||||
{
|
{
|
||||||
struct inode *dir = ovl_indexdir(dentry->d_sb)->d_inode;
|
struct inode *dir = ovl_indexdir(dentry->d_sb)->d_inode;
|
||||||
|
@ -469,6 +469,9 @@ static void ovl_cleanup_index(struct dentry *dentry)
|
||||||
err = PTR_ERR(index);
|
err = PTR_ERR(index);
|
||||||
if (!IS_ERR(index))
|
if (!IS_ERR(index))
|
||||||
err = ovl_cleanup(dir, index);
|
err = ovl_cleanup(dir, index);
|
||||||
|
else
|
||||||
|
index = NULL;
|
||||||
|
|
||||||
inode_unlock(dir);
|
inode_unlock(dir);
|
||||||
if (err)
|
if (err)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
@ -557,3 +560,22 @@ void ovl_nlink_end(struct dentry *dentry, bool locked)
|
||||||
mutex_unlock(&OVL_I(d_inode(dentry))->lock);
|
mutex_unlock(&OVL_I(d_inode(dentry))->lock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int ovl_lock_rename_workdir(struct dentry *workdir, struct dentry *upperdir)
|
||||||
|
{
|
||||||
|
/* Workdir should not be the same as upperdir */
|
||||||
|
if (workdir == upperdir)
|
||||||
|
goto err;
|
||||||
|
|
||||||
|
/* Workdir should not be subdir of upperdir and vice versa */
|
||||||
|
if (lock_rename(workdir, upperdir) != NULL)
|
||||||
|
goto err_unlock;
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
|
||||||
|
err_unlock:
|
||||||
|
unlock_rename(workdir, upperdir);
|
||||||
|
err:
|
||||||
|
pr_err("overlayfs: failed to lock workdir+upperdir\n");
|
||||||
|
return -EIO;
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue