diff options
-rw-r--r-- | fs/overlayfs/inode.c | 6 | ||||
-rw-r--r-- | fs/overlayfs/namei.c | 5 | ||||
-rw-r--r-- | fs/overlayfs/overlayfs.h | 3 | ||||
-rw-r--r-- | fs/overlayfs/super.c | 2 | ||||
-rw-r--r-- | fs/overlayfs/util.c | 66 |
5 files changed, 77 insertions, 5 deletions
diff --git a/fs/overlayfs/inode.c b/fs/overlayfs/inode.c index 196a4e5450c0..69f4fc26ee39 100644 --- a/fs/overlayfs/inode.c +++ b/fs/overlayfs/inode.c @@ -510,9 +510,9 @@ int ovl_set_nlink_lower(struct dentry *dentry) return ovl_set_nlink_common(dentry, ovl_dentry_lower(dentry), "L%+i"); } -static unsigned int ovl_get_nlink(struct dentry *lowerdentry, - struct dentry *upperdentry, - unsigned int fallback) +unsigned int ovl_get_nlink(struct dentry *lowerdentry, + struct dentry *upperdentry, + unsigned int fallback) { int nlink_diff; int nlink; diff --git a/fs/overlayfs/namei.c b/fs/overlayfs/namei.c index 2d8b6292fe21..9bc0e580a5b3 100644 --- a/fs/overlayfs/namei.c +++ b/fs/overlayfs/namei.c @@ -425,6 +425,11 @@ int ovl_verify_index(struct dentry *index, struct path *lowerstack, if (err) goto fail; + /* Check if index is orphan and don't warn before cleaning it */ + if (d_inode(index)->i_nlink == 1 && + ovl_get_nlink(index, origin.dentry, 0) == 0) + err = -ENOENT; + dput(origin.dentry); out: kfree(fh); diff --git a/fs/overlayfs/overlayfs.h b/fs/overlayfs/overlayfs.h index c1321ab38224..60d26605e039 100644 --- a/fs/overlayfs/overlayfs.h +++ b/fs/overlayfs/overlayfs.h @@ -263,6 +263,9 @@ int ovl_indexdir_cleanup(struct dentry *dentry, struct vfsmount *mnt, /* inode.c */ int ovl_set_nlink_upper(struct dentry *dentry); int ovl_set_nlink_lower(struct dentry *dentry); +unsigned int ovl_get_nlink(struct dentry *lowerdentry, + struct dentry *upperdentry, + unsigned int fallback); int ovl_setattr(struct dentry *dentry, struct iattr *attr); int ovl_getattr(const struct path *path, struct kstat *stat, u32 request_mask, unsigned int flags); diff --git a/fs/overlayfs/super.c b/fs/overlayfs/super.c index f29ee08cf99f..44dc2d6ffe0f 100644 --- a/fs/overlayfs/super.c +++ b/fs/overlayfs/super.c @@ -1069,7 +1069,7 @@ static int ovl_fill_super(struct super_block *sb, void *data, int silent) if (err) pr_err("overlayfs: failed to verify index dir origin\n"); - /* Cleanup bad/stale index entries */ + /* Cleanup bad/stale/orphan index entries */ if (!err) err = ovl_indexdir_cleanup(ufs->indexdir, ufs->upper_mnt, diff --git a/fs/overlayfs/util.c b/fs/overlayfs/util.c index 04d5018e728e..c492ba75c659 100644 --- a/fs/overlayfs/util.c +++ b/fs/overlayfs/util.c @@ -14,6 +14,8 @@ #include <linux/xattr.h> #include <linux/exportfs.h> #include <linux/uuid.h> +#include <linux/namei.h> +#include <linux/ratelimit.h> #include "overlayfs.h" #include "ovl_entry.h" @@ -411,6 +413,58 @@ void ovl_inuse_unlock(struct dentry *dentry) } } +/* Called must hold OVL_I(inode)->oi_lock */ +static void ovl_cleanup_index(struct dentry *dentry) +{ + struct inode *dir = ovl_indexdir(dentry->d_sb)->d_inode; + struct dentry *lowerdentry = ovl_dentry_lower(dentry); + struct dentry *upperdentry = ovl_dentry_upper(dentry); + struct dentry *index = NULL; + struct inode *inode; + struct qstr name; + int err; + + err = ovl_get_index_name(lowerdentry, &name); + if (err) + goto fail; + + inode = d_inode(upperdentry); + if (inode->i_nlink != 1) { + pr_warn_ratelimited("overlayfs: cleanup linked index (%pd2, ino=%lu, nlink=%u)\n", + upperdentry, inode->i_ino, inode->i_nlink); + /* + * We either have a bug with persistent union nlink or a lower + * hardlink was added while overlay is mounted. Adding a lower + * hardlink and then unlinking all overlay hardlinks would drop + * overlay nlink to zero before all upper inodes are unlinked. + * As a safety measure, when that situation is detected, set + * the overlay nlink to the index inode nlink minus one for the + * index entry itself. + */ + set_nlink(d_inode(dentry), inode->i_nlink - 1); + ovl_set_nlink_upper(dentry); + goto out; + } + + inode_lock_nested(dir, I_MUTEX_PARENT); + /* TODO: whiteout instead of cleanup to block future open by handle */ + index = lookup_one_len(name.name, ovl_indexdir(dentry->d_sb), name.len); + err = PTR_ERR(index); + if (!IS_ERR(index)) + err = ovl_cleanup(dir, index); + inode_unlock(dir); + if (err) + goto fail; + +out: + dput(index); + return; + +fail: + pr_err("overlayfs: cleanup index of '%pd2' failed (%i)\n", dentry, err); + goto out; +} + /* * Operations that change overlay inode and upper inode nlink need to be * synchronized with copy up for persistent nlink accounting. @@ -473,6 +527,16 @@ out: void ovl_nlink_end(struct dentry *dentry, bool locked) { - if (locked) + if (locked) { + if (ovl_test_flag(OVL_INDEX, d_inode(dentry)) && + d_inode(dentry)->i_nlink == 0) { + const struct cred *old_cred; + + old_cred = ovl_override_creds(dentry->d_sb); + ovl_cleanup_index(dentry); + revert_creds(old_cred); + } + mutex_unlock(&OVL_I(d_inode(dentry))->lock); + } } |