Commit 9c13e770 authored by Amir Goldstein's avatar Amir Goldstein Committed by Greg Kroah-Hartman

ovl: fix regression caused by overlapping layers detection

commit 0be0bfd2 upstream.

Once upon a time, commit 2cac0c00 ("ovl: get exclusive ownership on
upper/work dirs") in v4.13 added some sanity checks on overlayfs layers.
This change caused a docker regression. The root cause was mount leaks
by docker, which as far as I know, still exist.

To mitigate the regression, commit 85fdee1e ("ovl: fix regression
caused by exclusive upper/work dir protection") in v4.14 turned the
mount errors into warnings for the default index=off configuration.

Recently, commit 146d62e5 ("ovl: detect overlapping layers") in
v5.2, re-introduced exclusive upper/work dir checks regardless of
index=off configuration.

This changes the status quo and mount leak related bug reports have
started to re-surface. Restore the status quo to fix the regressions.
To clarify, index=off does NOT relax overlapping layers check for this
ovelayfs mount. index=off only relaxes exclusive upper/work dir checks
with another overlayfs mount.

To cover the part of overlapping layers detection that used the
exclusive upper/work dir checks to detect overlap with self upper/work
dir, add a trap also on the work base dir.

Link: https://github.com/moby/moby/issues/34672
Link: https://lore.kernel.org/linux-fsdevel/20171006121405.GA32700@veci.piliscsaba.szeredi.hu/
Link: https://github.com/containers/libpod/issues/3540
Fixes: 146d62e5 ("ovl: detect overlapping layers")
Cc: <stable@vger.kernel.org> # v4.19+
Signed-off-by: default avatarAmir Goldstein <amir73il@gmail.com>
Tested-by: default avatarColin Walters <walters@verbum.org>
Signed-off-by: default avatarMiklos Szeredi <mszeredi@redhat.com>
Signed-off-by: default avatarGreg Kroah-Hartman <gregkh@linuxfoundation.org>
parent 5e74396d
...@@ -302,7 +302,7 @@ beneath or above the path of another overlay lower layer path. ...@@ -302,7 +302,7 @@ 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 may 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 and may fail with EBUSY.
If files are accessed from two overlayfs mounts which share or overlap the 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, upper layer and/or workdir path the behavior of the overlay is undefined,
though it will not result in a crash or deadlock. though it will not result in a crash or deadlock.
......
...@@ -69,6 +69,7 @@ struct ovl_fs { ...@@ -69,6 +69,7 @@ struct ovl_fs {
bool workdir_locked; bool workdir_locked;
/* Traps in ovl inode cache */ /* Traps in ovl inode cache */
struct inode *upperdir_trap; struct inode *upperdir_trap;
struct inode *workbasedir_trap;
struct inode *workdir_trap; struct inode *workdir_trap;
struct inode *indexdir_trap; struct inode *indexdir_trap;
/* Inode numbers in all layers do not use the high xino_bits */ /* Inode numbers in all layers do not use the high xino_bits */
......
...@@ -217,6 +217,7 @@ static void ovl_free_fs(struct ovl_fs *ofs) ...@@ -217,6 +217,7 @@ static void ovl_free_fs(struct ovl_fs *ofs)
{ {
unsigned i; unsigned i;
iput(ofs->workbasedir_trap);
iput(ofs->indexdir_trap); iput(ofs->indexdir_trap);
iput(ofs->workdir_trap); iput(ofs->workdir_trap);
iput(ofs->upperdir_trap); iput(ofs->upperdir_trap);
...@@ -1007,6 +1008,25 @@ static int ovl_setup_trap(struct super_block *sb, struct dentry *dir, ...@@ -1007,6 +1008,25 @@ static int ovl_setup_trap(struct super_block *sb, struct dentry *dir,
return 0; return 0;
} }
/*
* Determine how we treat concurrent use of upperdir/workdir based on the
* index feature. This is papering over mount leaks of container runtimes,
* for example, an old overlay mount is leaked and now its upperdir is
* attempted to be used as a lower layer in a new overlay mount.
*/
static int ovl_report_in_use(struct ovl_fs *ofs, const char *name)
{
if (ofs->config.index) {
pr_err("overlayfs: %s is in-use as upperdir/workdir of another mount, mount with '-o index=off' to override exclusive upperdir protection.\n",
name);
return -EBUSY;
} else {
pr_warn("overlayfs: %s is in-use as upperdir/workdir of another mount, accessing files from both mounts will result in undefined behavior.\n",
name);
return 0;
}
}
static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs, static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
struct path *upperpath) struct path *upperpath)
{ {
...@@ -1044,14 +1064,12 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs, ...@@ -1044,14 +1064,12 @@ static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
upper_mnt->mnt_flags &= ~(MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME); upper_mnt->mnt_flags &= ~(MNT_NOATIME | MNT_NODIRATIME | MNT_RELATIME);
ofs->upper_mnt = upper_mnt; ofs->upper_mnt = upper_mnt;
err = -EBUSY;
if (ovl_inuse_trylock(ofs->upper_mnt->mnt_root)) { if (ovl_inuse_trylock(ofs->upper_mnt->mnt_root)) {
ofs->upperdir_locked = true; ofs->upperdir_locked = true;
} else if (ofs->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;
} else { } else {
pr_warn("overlayfs: upperdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n"); err = ovl_report_in_use(ofs, "upperdir");
if (err)
goto out;
} }
err = 0; err = 0;
...@@ -1161,16 +1179,19 @@ static int ovl_get_workdir(struct super_block *sb, struct ovl_fs *ofs, ...@@ -1161,16 +1179,19 @@ static int ovl_get_workdir(struct super_block *sb, struct ovl_fs *ofs,
ofs->workbasedir = dget(workpath.dentry); ofs->workbasedir = dget(workpath.dentry);
err = -EBUSY;
if (ovl_inuse_trylock(ofs->workbasedir)) { if (ovl_inuse_trylock(ofs->workbasedir)) {
ofs->workdir_locked = true; ofs->workdir_locked = true;
} else if (ofs->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;
} else { } else {
pr_warn("overlayfs: workdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n"); err = ovl_report_in_use(ofs, "workdir");
if (err)
goto out;
} }
err = ovl_setup_trap(sb, ofs->workbasedir, &ofs->workbasedir_trap,
"workdir");
if (err)
goto out;
err = ovl_make_workdir(sb, ofs, &workpath); err = ovl_make_workdir(sb, ofs, &workpath);
out: out:
...@@ -1289,16 +1310,16 @@ static int ovl_get_lower_layers(struct super_block *sb, struct ovl_fs *ofs, ...@@ -1289,16 +1310,16 @@ static int ovl_get_lower_layers(struct super_block *sb, struct ovl_fs *ofs,
if (err < 0) if (err < 0)
goto out; goto out;
err = -EBUSY;
if (ovl_is_inuse(stack[i].dentry)) {
pr_err("overlayfs: lowerdir is in-use as upperdir/workdir\n");
goto out;
}
err = ovl_setup_trap(sb, stack[i].dentry, &trap, "lowerdir"); err = ovl_setup_trap(sb, stack[i].dentry, &trap, "lowerdir");
if (err) if (err)
goto out; goto out;
if (ovl_is_inuse(stack[i].dentry)) {
err = ovl_report_in_use(ofs, "lowerdir");
if (err)
goto out;
}
mnt = clone_private_mount(&stack[i]); mnt = clone_private_mount(&stack[i]);
err = PTR_ERR(mnt); err = PTR_ERR(mnt);
if (IS_ERR(mnt)) { if (IS_ERR(mnt)) {
...@@ -1445,8 +1466,8 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb, ...@@ -1445,8 +1466,8 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
* - another layer of this overlayfs instance * - another layer of this overlayfs instance
* - upper/work dir of any overlayfs instance * - upper/work dir of any overlayfs instance
*/ */
static int ovl_check_layer(struct super_block *sb, struct dentry *dentry, static int ovl_check_layer(struct super_block *sb, struct ovl_fs *ofs,
const char *name) struct dentry *dentry, const char *name)
{ {
struct dentry *next = dentry, *parent; struct dentry *next = dentry, *parent;
int err = 0; int err = 0;
...@@ -1458,13 +1479,11 @@ static int ovl_check_layer(struct super_block *sb, struct dentry *dentry, ...@@ -1458,13 +1479,11 @@ static int ovl_check_layer(struct super_block *sb, struct dentry *dentry,
/* Walk back ancestors to root (inclusive) looking for traps */ /* Walk back ancestors to root (inclusive) looking for traps */
while (!err && parent != next) { while (!err && parent != next) {
if (ovl_is_inuse(parent)) { if (ovl_lookup_trap_inode(sb, parent)) {
err = -EBUSY;
pr_err("overlayfs: %s path overlapping in-use upperdir/workdir\n",
name);
} else if (ovl_lookup_trap_inode(sb, parent)) {
err = -ELOOP; err = -ELOOP;
pr_err("overlayfs: overlapping %s path\n", name); pr_err("overlayfs: overlapping %s path\n", name);
} else if (ovl_is_inuse(parent)) {
err = ovl_report_in_use(ofs, name);
} }
next = parent; next = parent;
parent = dget_parent(next); parent = dget_parent(next);
...@@ -1485,7 +1504,8 @@ static int ovl_check_overlapping_layers(struct super_block *sb, ...@@ -1485,7 +1504,8 @@ static int ovl_check_overlapping_layers(struct super_block *sb,
int i, err; int i, err;
if (ofs->upper_mnt) { if (ofs->upper_mnt) {
err = ovl_check_layer(sb, ofs->upper_mnt->mnt_root, "upperdir"); err = ovl_check_layer(sb, ofs, ofs->upper_mnt->mnt_root,
"upperdir");
if (err) if (err)
return err; return err;
...@@ -1496,13 +1516,14 @@ static int ovl_check_overlapping_layers(struct super_block *sb, ...@@ -1496,13 +1516,14 @@ static int ovl_check_overlapping_layers(struct super_block *sb,
* workbasedir. In that case, we already have their traps in * workbasedir. In that case, we already have their traps in
* inode cache and we will catch that case on lookup. * inode cache and we will catch that case on lookup.
*/ */
err = ovl_check_layer(sb, ofs->workbasedir, "workdir"); err = ovl_check_layer(sb, ofs, ofs->workbasedir, "workdir");
if (err) if (err)
return err; return err;
} }
for (i = 0; i < ofs->numlower; i++) { for (i = 0; i < ofs->numlower; i++) {
err = ovl_check_layer(sb, ofs->lower_layers[i].mnt->mnt_root, err = ovl_check_layer(sb, ofs,
ofs->lower_layers[i].mnt->mnt_root,
"lowerdir"); "lowerdir");
if (err) if (err)
return err; return err;
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment