Skip to content

Commit 31747ed

Browse files
amir73ilMiklos Szeredi
authored andcommitted
ovl: hash directory inodes for fsnotify
fsnotify pins a watched directory inode in cache, but if directory dentry is released, new lookup will allocate a new dentry and a new inode. Directory events will be notified on the new inode, while fsnotify listener is watching the old pinned inode. Hash all directory inodes to reuse the pinned inode on lookup. Pure upper dirs are hashes by real upper inode, merge and lower dirs are hashed by real lower inode. The reference to lower inode was being held by the lower dentry object in the overlay dentry (oe->lowerstack[0]). Releasing the overlay dentry may drop lower inode refcount to zero. Add a refcount on behalf of the overlay inode to prevent that. As a by-product, hashing directory inodes also detects multiple redirected dirs to the same lower dir and uncovered redirected dir target on and returns -ESTALE on lookup. The reported issue dates back to initial version of overlayfs, but this patch depends on ovl_inode code that was introduced in kernel v4.13. Cc: <[email protected]> #v4.13 Reported-by: Niklas Cassel <[email protected]> Signed-off-by: Amir Goldstein <[email protected]> Signed-off-by: Miklos Szeredi <[email protected]> Tested-by: Niklas Cassel <[email protected]>
1 parent a8750dd commit 31747ed

File tree

3 files changed

+31
-13
lines changed

3 files changed

+31
-13
lines changed

fs/overlayfs/inode.c

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -606,6 +606,16 @@ static int ovl_inode_set(struct inode *inode, void *data)
606606
static bool ovl_verify_inode(struct inode *inode, struct dentry *lowerdentry,
607607
struct dentry *upperdentry)
608608
{
609+
if (S_ISDIR(inode->i_mode)) {
610+
/* Real lower dir moved to upper layer under us? */
611+
if (!lowerdentry && ovl_inode_lower(inode))
612+
return false;
613+
614+
/* Lookup of an uncovered redirect origin? */
615+
if (!upperdentry && ovl_inode_upper(inode))
616+
return false;
617+
}
618+
609619
/*
610620
* Allow non-NULL lower inode in ovl_inode even if lowerdentry is NULL.
611621
* This happens when finding a copied up overlay inode for a renamed
@@ -633,6 +643,8 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry,
633643
struct inode *inode;
634644
/* Already indexed or could be indexed on copy up? */
635645
bool indexed = (index || (ovl_indexdir(dentry->d_sb) && !upperdentry));
646+
struct dentry *origin = indexed ? lowerdentry : NULL;
647+
bool is_dir;
636648

637649
if (WARN_ON(upperdentry && indexed && !lowerdentry))
638650
return ERR_PTR(-EIO);
@@ -641,15 +653,19 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry,
641653
realinode = d_inode(lowerdentry);
642654

643655
/*
644-
* Copy up origin (lower) may exist for non-indexed upper, but we must
645-
* not use lower as hash key in that case.
646-
* Hash inodes that are or could be indexed by origin inode and
647-
* non-indexed upper inodes that could be hard linked by upper inode.
656+
* Copy up origin (lower) may exist for non-indexed non-dir upper, but
657+
* we must not use lower as hash key in that case.
658+
* Hash non-dir that is or could be indexed by origin inode.
659+
* Hash dir that is or could be merged by origin inode.
660+
* Hash pure upper and non-indexed non-dir by upper inode.
648661
*/
649-
if (!S_ISDIR(realinode->i_mode) && (upperdentry || indexed)) {
650-
struct inode *key = d_inode(indexed ? lowerdentry :
651-
upperdentry);
652-
unsigned int nlink;
662+
is_dir = S_ISDIR(realinode->i_mode);
663+
if (is_dir)
664+
origin = lowerdentry;
665+
666+
if (upperdentry || origin) {
667+
struct inode *key = d_inode(origin ?: upperdentry);
668+
unsigned int nlink = is_dir ? 1 : realinode->i_nlink;
653669

654670
inode = iget5_locked(dentry->d_sb, (unsigned long) key,
655671
ovl_inode_test, ovl_inode_set, key);
@@ -670,8 +686,9 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry,
670686
goto out;
671687
}
672688

673-
nlink = ovl_get_nlink(lowerdentry, upperdentry,
674-
realinode->i_nlink);
689+
/* Recalculate nlink for non-dir due to indexing */
690+
if (!is_dir)
691+
nlink = ovl_get_nlink(lowerdentry, upperdentry, nlink);
675692
set_nlink(inode, nlink);
676693
} else {
677694
inode = new_inode(dentry->d_sb);
@@ -685,7 +702,7 @@ struct inode *ovl_get_inode(struct dentry *dentry, struct dentry *upperdentry,
685702
ovl_set_flag(OVL_IMPURE, inode);
686703

687704
/* Check for non-merge dir that may have whiteouts */
688-
if (S_ISDIR(realinode->i_mode)) {
705+
if (is_dir) {
689706
struct ovl_entry *oe = dentry->d_fsdata;
690707

691708
if (((upperdentry && lowerdentry) || oe->numlower > 1) ||

fs/overlayfs/super.c

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,7 @@ static void ovl_destroy_inode(struct inode *inode)
211211
struct ovl_inode *oi = OVL_I(inode);
212212

213213
dput(oi->__upperdentry);
214+
iput(oi->lower);
214215
kfree(oi->redirect);
215216
ovl_dir_cache_free(inode);
216217
mutex_destroy(&oi->lock);

fs/overlayfs/util.c

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,7 +257,7 @@ void ovl_inode_init(struct inode *inode, struct dentry *upperdentry,
257257
if (upperdentry)
258258
OVL_I(inode)->__upperdentry = upperdentry;
259259
if (lowerdentry)
260-
OVL_I(inode)->lower = d_inode(lowerdentry);
260+
OVL_I(inode)->lower = igrab(d_inode(lowerdentry));
261261

262262
ovl_copyattr(d_inode(upperdentry ?: lowerdentry), inode);
263263
}
@@ -273,7 +273,7 @@ void ovl_inode_update(struct inode *inode, struct dentry *upperdentry)
273273
*/
274274
smp_wmb();
275275
OVL_I(inode)->__upperdentry = upperdentry;
276-
if (!S_ISDIR(upperinode->i_mode) && inode_unhashed(inode)) {
276+
if (inode_unhashed(inode)) {
277277
inode->i_private = upperinode;
278278
__insert_inode_hash(inode, (unsigned long) upperinode);
279279
}

0 commit comments

Comments
 (0)