overlayfs fixes for 5.2-rc4
-----BEGIN PGP SIGNATURE----- iHUEABYIAB0WIQSQHSd0lITzzeNWNm3h3BK/laaZPAUCXPkV+wAKCRDh3BK/laaZ PMGyAQDq6ry0bTRPIL52Ek+eRS/pi3bIsH96e22Q6W/NrAQEfwD+NtFZneAW/Tux AuKIRWqS7UdqCjLurwMHfR9bOHrBDQI= =z7pu -----END PGP SIGNATURE----- Merge tag 'ovl-fixes-5.2-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs Pull overlayfs fixes from Miklos Szeredi: "Here's one fix for a class of bugs triggered by syzcaller, and one that makes xfstests fail less" * tag 'ovl-fixes-5.2-rc4' of git://git.kernel.org/pub/scm/linux/kernel/git/mszeredi/vfs: ovl: doc: add non-standard corner cases ovl: detect overlapping layers ovl: support the FS_IOC_FS[SG]ETXATTR ioctls
This commit is contained in:
commit
5d6b501fe5
|
@ -336,8 +336,20 @@ the copied layers will fail the verification of the lower root file handle.
|
|||
Non-standard behavior
|
||||
---------------------
|
||||
|
||||
Overlayfs can now act as a POSIX compliant filesystem with the following
|
||||
features turned on:
|
||||
Current version of overlayfs can act as a mostly POSIX compliant
|
||||
filesystem.
|
||||
|
||||
This is the list of cases that overlayfs doesn't currently handle:
|
||||
|
||||
a) POSIX mandates updating st_atime for reads. This is currently not
|
||||
done in the case when the file resides on a lower layer.
|
||||
|
||||
b) If a file residing on a lower layer is opened for read-only and then
|
||||
memory mapped with MAP_SHARED, then subsequent changes to the file are not
|
||||
reflected in the memory mapping.
|
||||
|
||||
The following options allow overlayfs to act more like a standards
|
||||
compliant filesystem:
|
||||
|
||||
1) "redirect_dir"
|
||||
|
||||
|
|
|
@ -426,7 +426,8 @@ static unsigned int ovl_get_inode_flags(struct inode *inode)
|
|||
return ovl_iflags;
|
||||
}
|
||||
|
||||
static long ovl_ioctl_set_flags(struct file *file, unsigned long arg)
|
||||
static long ovl_ioctl_set_flags(struct file *file, unsigned int cmd,
|
||||
unsigned long arg)
|
||||
{
|
||||
long ret;
|
||||
struct inode *inode = file_inode(file);
|
||||
|
@ -456,7 +457,7 @@ static long ovl_ioctl_set_flags(struct file *file, unsigned long arg)
|
|||
if (ret)
|
||||
goto unlock;
|
||||
|
||||
ret = ovl_real_ioctl(file, FS_IOC_SETFLAGS, arg);
|
||||
ret = ovl_real_ioctl(file, cmd, arg);
|
||||
|
||||
ovl_copyflags(ovl_inode_real(inode), inode);
|
||||
unlock:
|
||||
|
@ -474,11 +475,13 @@ static long ovl_ioctl(struct file *file, unsigned int cmd, unsigned long arg)
|
|||
|
||||
switch (cmd) {
|
||||
case FS_IOC_GETFLAGS:
|
||||
case FS_IOC_FSGETXATTR:
|
||||
ret = ovl_real_ioctl(file, cmd, arg);
|
||||
break;
|
||||
|
||||
case FS_IOC_SETFLAGS:
|
||||
ret = ovl_ioctl_set_flags(file, arg);
|
||||
case FS_IOC_FSSETXATTR:
|
||||
ret = ovl_ioctl_set_flags(file, cmd, arg);
|
||||
break;
|
||||
|
||||
default:
|
||||
|
|
|
@ -777,6 +777,54 @@ struct inode *ovl_lookup_inode(struct super_block *sb, struct dentry *real,
|
|||
return inode;
|
||||
}
|
||||
|
||||
bool ovl_lookup_trap_inode(struct super_block *sb, struct dentry *dir)
|
||||
{
|
||||
struct inode *key = d_inode(dir);
|
||||
struct inode *trap;
|
||||
bool res;
|
||||
|
||||
trap = ilookup5(sb, (unsigned long) key, ovl_inode_test, key);
|
||||
if (!trap)
|
||||
return false;
|
||||
|
||||
res = IS_DEADDIR(trap) && !ovl_inode_upper(trap) &&
|
||||
!ovl_inode_lower(trap);
|
||||
|
||||
iput(trap);
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create an inode cache entry for layer root dir, that will intentionally
|
||||
* fail ovl_verify_inode(), so any lookup that will find some layer root
|
||||
* will fail.
|
||||
*/
|
||||
struct inode *ovl_get_trap_inode(struct super_block *sb, struct dentry *dir)
|
||||
{
|
||||
struct inode *key = d_inode(dir);
|
||||
struct inode *trap;
|
||||
|
||||
if (!d_is_dir(dir))
|
||||
return ERR_PTR(-ENOTDIR);
|
||||
|
||||
trap = iget5_locked(sb, (unsigned long) key, ovl_inode_test,
|
||||
ovl_inode_set, key);
|
||||
if (!trap)
|
||||
return ERR_PTR(-ENOMEM);
|
||||
|
||||
if (!(trap->i_state & I_NEW)) {
|
||||
/* Conflicting layer roots? */
|
||||
iput(trap);
|
||||
return ERR_PTR(-ELOOP);
|
||||
}
|
||||
|
||||
trap->i_mode = S_IFDIR;
|
||||
trap->i_flags = S_DEAD;
|
||||
unlock_new_inode(trap);
|
||||
|
||||
return trap;
|
||||
}
|
||||
|
||||
/*
|
||||
* Does overlay inode need to be hashed by lower inode?
|
||||
*/
|
||||
|
|
|
@ -18,6 +18,7 @@
|
|||
#include "overlayfs.h"
|
||||
|
||||
struct ovl_lookup_data {
|
||||
struct super_block *sb;
|
||||
struct qstr name;
|
||||
bool is_dir;
|
||||
bool opaque;
|
||||
|
@ -244,6 +245,12 @@ static int ovl_lookup_single(struct dentry *base, struct ovl_lookup_data *d,
|
|||
if (!d->metacopy || d->last)
|
||||
goto out;
|
||||
} else {
|
||||
if (ovl_lookup_trap_inode(d->sb, this)) {
|
||||
/* Caught in a trap of overlapping layers */
|
||||
err = -ELOOP;
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
if (last_element)
|
||||
d->is_dir = true;
|
||||
if (d->last)
|
||||
|
@ -819,6 +826,7 @@ struct dentry *ovl_lookup(struct inode *dir, struct dentry *dentry,
|
|||
int err;
|
||||
bool metacopy = false;
|
||||
struct ovl_lookup_data d = {
|
||||
.sb = dentry->d_sb,
|
||||
.name = dentry->d_name,
|
||||
.is_dir = false,
|
||||
.opaque = false,
|
||||
|
|
|
@ -270,6 +270,7 @@ void ovl_clear_flag(unsigned long flag, struct inode *inode);
|
|||
bool ovl_test_flag(unsigned long flag, struct inode *inode);
|
||||
bool ovl_inuse_trylock(struct dentry *dentry);
|
||||
void ovl_inuse_unlock(struct dentry *dentry);
|
||||
bool ovl_is_inuse(struct dentry *dentry);
|
||||
bool ovl_need_index(struct dentry *dentry);
|
||||
int ovl_nlink_start(struct dentry *dentry);
|
||||
void ovl_nlink_end(struct dentry *dentry);
|
||||
|
@ -376,6 +377,8 @@ struct ovl_inode_params {
|
|||
struct inode *ovl_new_inode(struct super_block *sb, umode_t mode, dev_t rdev);
|
||||
struct inode *ovl_lookup_inode(struct super_block *sb, struct dentry *real,
|
||||
bool is_upper);
|
||||
bool ovl_lookup_trap_inode(struct super_block *sb, struct dentry *dir);
|
||||
struct inode *ovl_get_trap_inode(struct super_block *sb, struct dentry *dir);
|
||||
struct inode *ovl_get_inode(struct super_block *sb,
|
||||
struct ovl_inode_params *oip);
|
||||
static inline void ovl_copyattr(struct inode *from, struct inode *to)
|
||||
|
|
|
@ -29,6 +29,8 @@ struct ovl_sb {
|
|||
|
||||
struct ovl_layer {
|
||||
struct vfsmount *mnt;
|
||||
/* Trap in ovl inode cache */
|
||||
struct inode *trap;
|
||||
struct ovl_sb *fs;
|
||||
/* Index of this layer in fs root (upper idx == 0) */
|
||||
int idx;
|
||||
|
@ -65,6 +67,10 @@ struct ovl_fs {
|
|||
/* Did we take the inuse lock? */
|
||||
bool upperdir_locked;
|
||||
bool workdir_locked;
|
||||
/* Traps in ovl inode cache */
|
||||
struct inode *upperdir_trap;
|
||||
struct inode *workdir_trap;
|
||||
struct inode *indexdir_trap;
|
||||
/* Inode numbers in all layers do not use the high xino_bits */
|
||||
unsigned int xino_bits;
|
||||
};
|
||||
|
|
|
@ -215,6 +215,9 @@ static void ovl_free_fs(struct ovl_fs *ofs)
|
|||
{
|
||||
unsigned i;
|
||||
|
||||
iput(ofs->indexdir_trap);
|
||||
iput(ofs->workdir_trap);
|
||||
iput(ofs->upperdir_trap);
|
||||
dput(ofs->indexdir);
|
||||
dput(ofs->workdir);
|
||||
if (ofs->workdir_locked)
|
||||
|
@ -223,8 +226,10 @@ static void ovl_free_fs(struct ovl_fs *ofs)
|
|||
if (ofs->upperdir_locked)
|
||||
ovl_inuse_unlock(ofs->upper_mnt->mnt_root);
|
||||
mntput(ofs->upper_mnt);
|
||||
for (i = 0; i < ofs->numlower; i++)
|
||||
for (i = 0; i < ofs->numlower; i++) {
|
||||
iput(ofs->lower_layers[i].trap);
|
||||
mntput(ofs->lower_layers[i].mnt);
|
||||
}
|
||||
for (i = 0; i < ofs->numlowerfs; i++)
|
||||
free_anon_bdev(ofs->lower_fs[i].pseudo_dev);
|
||||
kfree(ofs->lower_layers);
|
||||
|
@ -983,7 +988,26 @@ static const struct xattr_handler *ovl_xattr_handlers[] = {
|
|||
NULL
|
||||
};
|
||||
|
||||
static int ovl_get_upper(struct ovl_fs *ofs, struct path *upperpath)
|
||||
static int ovl_setup_trap(struct super_block *sb, struct dentry *dir,
|
||||
struct inode **ptrap, const char *name)
|
||||
{
|
||||
struct inode *trap;
|
||||
int err;
|
||||
|
||||
trap = ovl_get_trap_inode(sb, dir);
|
||||
err = PTR_ERR(trap);
|
||||
if (IS_ERR(trap)) {
|
||||
if (err == -ELOOP)
|
||||
pr_err("overlayfs: conflicting %s path\n", name);
|
||||
return err;
|
||||
}
|
||||
|
||||
*ptrap = trap;
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ovl_get_upper(struct super_block *sb, struct ovl_fs *ofs,
|
||||
struct path *upperpath)
|
||||
{
|
||||
struct vfsmount *upper_mnt;
|
||||
int err;
|
||||
|
@ -1003,6 +1027,11 @@ static int ovl_get_upper(struct ovl_fs *ofs, struct path *upperpath)
|
|||
if (err)
|
||||
goto out;
|
||||
|
||||
err = ovl_setup_trap(sb, upperpath->dentry, &ofs->upperdir_trap,
|
||||
"upperdir");
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
upper_mnt = clone_private_mount(upperpath);
|
||||
err = PTR_ERR(upper_mnt);
|
||||
if (IS_ERR(upper_mnt)) {
|
||||
|
@ -1029,7 +1058,8 @@ out:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
|
||||
static int ovl_make_workdir(struct super_block *sb, struct ovl_fs *ofs,
|
||||
struct path *workpath)
|
||||
{
|
||||
struct vfsmount *mnt = ofs->upper_mnt;
|
||||
struct dentry *temp;
|
||||
|
@ -1044,6 +1074,10 @@ static int ovl_make_workdir(struct ovl_fs *ofs, struct path *workpath)
|
|||
if (!ofs->workdir)
|
||||
goto out;
|
||||
|
||||
err = ovl_setup_trap(sb, ofs->workdir, &ofs->workdir_trap, "workdir");
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Upper should support d_type, else whiteouts are visible. Given
|
||||
* workdir and upper are on same fs, we can do iterate_dir() on
|
||||
|
@ -1104,7 +1138,8 @@ out:
|
|||
return err;
|
||||
}
|
||||
|
||||
static int ovl_get_workdir(struct ovl_fs *ofs, struct path *upperpath)
|
||||
static int ovl_get_workdir(struct super_block *sb, struct ovl_fs *ofs,
|
||||
struct path *upperpath)
|
||||
{
|
||||
int err;
|
||||
struct path workpath = { };
|
||||
|
@ -1135,19 +1170,16 @@ static int ovl_get_workdir(struct ovl_fs *ofs, struct path *upperpath)
|
|||
pr_warn("overlayfs: workdir is in-use by another mount, accessing files from both mounts will result in undefined behavior.\n");
|
||||
}
|
||||
|
||||
err = ovl_make_workdir(ofs, &workpath);
|
||||
if (err)
|
||||
goto out;
|
||||
err = ovl_make_workdir(sb, ofs, &workpath);
|
||||
|
||||
err = 0;
|
||||
out:
|
||||
path_put(&workpath);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
static int ovl_get_indexdir(struct ovl_fs *ofs, struct ovl_entry *oe,
|
||||
struct path *upperpath)
|
||||
static int ovl_get_indexdir(struct super_block *sb, struct ovl_fs *ofs,
|
||||
struct ovl_entry *oe, struct path *upperpath)
|
||||
{
|
||||
struct vfsmount *mnt = ofs->upper_mnt;
|
||||
int err;
|
||||
|
@ -1166,6 +1198,11 @@ static int ovl_get_indexdir(struct ovl_fs *ofs, struct ovl_entry *oe,
|
|||
|
||||
ofs->indexdir = ovl_workdir_create(ofs, OVL_INDEXDIR_NAME, true);
|
||||
if (ofs->indexdir) {
|
||||
err = ovl_setup_trap(sb, ofs->indexdir, &ofs->indexdir_trap,
|
||||
"indexdir");
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
/*
|
||||
* Verify upper root is exclusively associated with index dir.
|
||||
* Older kernels stored upper fh in "trusted.overlay.origin"
|
||||
|
@ -1253,8 +1290,8 @@ static int ovl_get_fsid(struct ovl_fs *ofs, const struct path *path)
|
|||
return ofs->numlowerfs;
|
||||
}
|
||||
|
||||
static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
||||
unsigned int numlower)
|
||||
static int ovl_get_lower_layers(struct super_block *sb, struct ovl_fs *ofs,
|
||||
struct path *stack, unsigned int numlower)
|
||||
{
|
||||
int err;
|
||||
unsigned int i;
|
||||
|
@ -1272,16 +1309,28 @@ static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
|||
|
||||
for (i = 0; i < numlower; i++) {
|
||||
struct vfsmount *mnt;
|
||||
struct inode *trap;
|
||||
int fsid;
|
||||
|
||||
err = fsid = ovl_get_fsid(ofs, &stack[i]);
|
||||
if (err < 0)
|
||||
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");
|
||||
if (err)
|
||||
goto out;
|
||||
|
||||
mnt = clone_private_mount(&stack[i]);
|
||||
err = PTR_ERR(mnt);
|
||||
if (IS_ERR(mnt)) {
|
||||
pr_err("overlayfs: failed to clone lowerpath\n");
|
||||
iput(trap);
|
||||
goto out;
|
||||
}
|
||||
|
||||
|
@ -1291,6 +1340,7 @@ static int ovl_get_lower_layers(struct ovl_fs *ofs, struct path *stack,
|
|||
*/
|
||||
mnt->mnt_flags |= MNT_READONLY | MNT_NOATIME;
|
||||
|
||||
ofs->lower_layers[ofs->numlower].trap = trap;
|
||||
ofs->lower_layers[ofs->numlower].mnt = mnt;
|
||||
ofs->lower_layers[ofs->numlower].idx = i + 1;
|
||||
ofs->lower_layers[ofs->numlower].fsid = fsid;
|
||||
|
@ -1385,7 +1435,7 @@ static struct ovl_entry *ovl_get_lowerstack(struct super_block *sb,
|
|||
goto out_err;
|
||||
}
|
||||
|
||||
err = ovl_get_lower_layers(ofs, stack, numlower);
|
||||
err = ovl_get_lower_layers(sb, ofs, stack, numlower);
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
|
@ -1417,6 +1467,85 @@ out_err:
|
|||
goto out;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if this layer root is a descendant of:
|
||||
* - another layer of this overlayfs instance
|
||||
* - upper/work dir of any overlayfs instance
|
||||
* - a disconnected dentry (detached root)
|
||||
*/
|
||||
static int ovl_check_layer(struct super_block *sb, struct dentry *dentry,
|
||||
const char *name)
|
||||
{
|
||||
struct dentry *next, *parent;
|
||||
bool is_root = false;
|
||||
int err = 0;
|
||||
|
||||
if (!dentry || dentry == dentry->d_sb->s_root)
|
||||
return 0;
|
||||
|
||||
next = dget(dentry);
|
||||
/* Walk back ancestors to fs root (inclusive) looking for traps */
|
||||
do {
|
||||
parent = dget_parent(next);
|
||||
is_root = (parent == next);
|
||||
if (ovl_is_inuse(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;
|
||||
pr_err("overlayfs: overlapping %s path\n", name);
|
||||
}
|
||||
dput(next);
|
||||
next = parent;
|
||||
} while (!err && !is_root);
|
||||
|
||||
/* Did we really walk to fs root or found a detached root? */
|
||||
if (!err && next != dentry->d_sb->s_root) {
|
||||
err = -ESTALE;
|
||||
pr_err("overlayfs: disconnected %s path\n", name);
|
||||
}
|
||||
|
||||
dput(next);
|
||||
|
||||
return err;
|
||||
}
|
||||
|
||||
/*
|
||||
* Check if any of the layers or work dirs overlap.
|
||||
*/
|
||||
static int ovl_check_overlapping_layers(struct super_block *sb,
|
||||
struct ovl_fs *ofs)
|
||||
{
|
||||
int i, err;
|
||||
|
||||
if (ofs->upper_mnt) {
|
||||
err = ovl_check_layer(sb, ofs->upper_mnt->mnt_root, "upperdir");
|
||||
if (err)
|
||||
return err;
|
||||
|
||||
/*
|
||||
* Checking workbasedir avoids hitting ovl_is_inuse(parent) of
|
||||
* this instance and covers overlapping work and index dirs,
|
||||
* unless work or index dir have been moved since created inside
|
||||
* workbasedir. In that case, we already have their traps in
|
||||
* inode cache and we will catch that case on lookup.
|
||||
*/
|
||||
err = ovl_check_layer(sb, ofs->workbasedir, "workdir");
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
for (i = 0; i < ofs->numlower; i++) {
|
||||
err = ovl_check_layer(sb, ofs->lower_layers[i].mnt->mnt_root,
|
||||
"lowerdir");
|
||||
if (err)
|
||||
return err;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
||||
{
|
||||
struct path upperpath = { };
|
||||
|
@ -1456,17 +1585,20 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|||
if (ofs->config.xino != OVL_XINO_OFF)
|
||||
ofs->xino_bits = BITS_PER_LONG - 32;
|
||||
|
||||
/* alloc/destroy_inode needed for setting up traps in inode cache */
|
||||
sb->s_op = &ovl_super_operations;
|
||||
|
||||
if (ofs->config.upperdir) {
|
||||
if (!ofs->config.workdir) {
|
||||
pr_err("overlayfs: missing 'workdir'\n");
|
||||
goto out_err;
|
||||
}
|
||||
|
||||
err = ovl_get_upper(ofs, &upperpath);
|
||||
err = ovl_get_upper(sb, ofs, &upperpath);
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
err = ovl_get_workdir(ofs, &upperpath);
|
||||
err = ovl_get_workdir(sb, ofs, &upperpath);
|
||||
if (err)
|
||||
goto out_err;
|
||||
|
||||
|
@ -1487,7 +1619,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|||
sb->s_flags |= SB_RDONLY;
|
||||
|
||||
if (!(ovl_force_readonly(ofs)) && ofs->config.index) {
|
||||
err = ovl_get_indexdir(ofs, oe, &upperpath);
|
||||
err = ovl_get_indexdir(sb, ofs, oe, &upperpath);
|
||||
if (err)
|
||||
goto out_free_oe;
|
||||
|
||||
|
@ -1500,6 +1632,10 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|||
|
||||
}
|
||||
|
||||
err = ovl_check_overlapping_layers(sb, ofs);
|
||||
if (err)
|
||||
goto out_free_oe;
|
||||
|
||||
/* Show index=off in /proc/mounts for forced r/o mount */
|
||||
if (!ofs->indexdir) {
|
||||
ofs->config.index = false;
|
||||
|
@ -1521,7 +1657,6 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent)
|
|||
cap_lower(cred->cap_effective, CAP_SYS_RESOURCE);
|
||||
|
||||
sb->s_magic = OVERLAYFS_SUPER_MAGIC;
|
||||
sb->s_op = &ovl_super_operations;
|
||||
sb->s_xattr = ovl_xattr_handlers;
|
||||
sb->s_fs_info = ofs;
|
||||
sb->s_flags |= SB_POSIXACL;
|
||||
|
|
|
@ -652,6 +652,18 @@ void ovl_inuse_unlock(struct dentry *dentry)
|
|||
}
|
||||
}
|
||||
|
||||
bool ovl_is_inuse(struct dentry *dentry)
|
||||
{
|
||||
struct inode *inode = d_inode(dentry);
|
||||
bool inuse;
|
||||
|
||||
spin_lock(&inode->i_lock);
|
||||
inuse = (inode->i_state & I_OVL_INUSE);
|
||||
spin_unlock(&inode->i_lock);
|
||||
|
||||
return inuse;
|
||||
}
|
||||
|
||||
/*
|
||||
* Does this overlay dentry need to be indexed on copy up?
|
||||
*/
|
||||
|
|
Loading…
Reference in New Issue