diff options
Diffstat (limited to 'target/linux')
-rw-r--r-- | target/linux/generic/patches-4.0/140-overlayfs_readdir_locking_fix.patch | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/target/linux/generic/patches-4.0/140-overlayfs_readdir_locking_fix.patch b/target/linux/generic/patches-4.0/140-overlayfs_readdir_locking_fix.patch new file mode 100644 index 0000000000..67dff987d3 --- /dev/null +++ b/target/linux/generic/patches-4.0/140-overlayfs_readdir_locking_fix.patch @@ -0,0 +1,148 @@ +Patch by: Miklos Szeredi <miklos@szeredi.hu> + +Some filesystems (e.g. jffs2) lock the same resources for both readdir +and lookup, leading to a deadlock in ovl_cache_entry_new, which is called +from the filldir, and calls lookup itself. + +--- a/fs/overlayfs/readdir.c ++++ b/fs/overlayfs/readdir.c +@@ -23,6 +23,7 @@ struct ovl_cache_entry { + u64 ino; + struct list_head l_node; + struct rb_node node; ++ struct ovl_cache_entry *next_maybe_whiteout; + bool is_whiteout; + char name[]; + }; +@@ -39,7 +40,7 @@ struct ovl_readdir_data { + struct rb_root root; + struct list_head *list; + struct list_head middle; +- struct dentry *dir; ++ struct ovl_cache_entry *first_maybe_whiteout; + int count; + int err; + }; +@@ -79,7 +80,7 @@ static struct ovl_cache_entry *ovl_cache + return NULL; + } + +-static struct ovl_cache_entry *ovl_cache_entry_new(struct dentry *dir, ++static struct ovl_cache_entry *ovl_cache_entry_new(struct ovl_readdir_data *rdd, + const char *name, int len, + u64 ino, unsigned int d_type) + { +@@ -98,29 +99,8 @@ static struct ovl_cache_entry *ovl_cache + p->is_whiteout = false; + + if (d_type == DT_CHR) { +- struct dentry *dentry; +- const struct cred *old_cred; +- struct cred *override_cred; +- +- override_cred = prepare_creds(); +- if (!override_cred) { +- kfree(p); +- return NULL; +- } +- +- /* +- * CAP_DAC_OVERRIDE for lookup +- */ +- cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); +- old_cred = override_creds(override_cred); +- +- dentry = lookup_one_len(name, dir, len); +- if (!IS_ERR(dentry)) { +- p->is_whiteout = ovl_is_whiteout(dentry); +- dput(dentry); +- } +- revert_creds(old_cred); +- put_cred(override_cred); ++ p->next_maybe_whiteout = rdd->first_maybe_whiteout; ++ rdd->first_maybe_whiteout = p; + } + return p; + } +@@ -148,7 +128,7 @@ static int ovl_cache_entry_add_rb(struct + return 0; + } + +- p = ovl_cache_entry_new(rdd->dir, name, len, ino, d_type); ++ p = ovl_cache_entry_new(rdd, name, len, ino, d_type); + if (p == NULL) + return -ENOMEM; + +@@ -169,7 +149,7 @@ static int ovl_fill_lower(struct ovl_rea + if (p) { + list_move_tail(&p->l_node, &rdd->middle); + } else { +- p = ovl_cache_entry_new(rdd->dir, name, namelen, ino, d_type); ++ p = ovl_cache_entry_new(rdd, name, namelen, ino, d_type); + if (p == NULL) + rdd->err = -ENOMEM; + else +@@ -219,6 +199,43 @@ static int ovl_fill_merge(struct dir_con + return ovl_fill_lower(rdd, name, namelen, offset, ino, d_type); + } + ++static int ovl_check_whiteouts(struct dentry *dir, struct ovl_readdir_data *rdd) ++{ ++ int err = 0; ++ ++ mutex_lock(&dir->d_inode->i_mutex); ++ while (rdd->first_maybe_whiteout) { ++ struct dentry *dentry; ++ const struct cred *old_cred; ++ struct cred *override_cred; ++ struct ovl_cache_entry *p = rdd->first_maybe_whiteout; ++ ++ rdd->first_maybe_whiteout = p->next_maybe_whiteout; ++ ++ override_cred = prepare_creds(); ++ if (!override_cred) { ++ err = -ENOMEM; ++ break; ++ } ++ /* ++ * CAP_DAC_OVERRIDE for lookup ++ */ ++ cap_raise(override_cred->cap_effective, CAP_DAC_OVERRIDE); ++ old_cred = override_creds(override_cred); ++ ++ dentry = lookup_one_len(p->name, dir, p->len); ++ if (!IS_ERR(dentry)) { ++ p->is_whiteout = ovl_is_whiteout(dentry); ++ dput(dentry); ++ } ++ revert_creds(old_cred); ++ put_cred(override_cred); ++ } ++ mutex_unlock(&dir->d_inode->i_mutex); ++ ++ return err; ++} ++ + static inline int ovl_dir_read(struct path *realpath, + struct ovl_readdir_data *rdd) + { +@@ -229,7 +246,7 @@ static inline int ovl_dir_read(struct pa + if (IS_ERR(realfile)) + return PTR_ERR(realfile); + +- rdd->dir = realpath->dentry; ++ rdd->first_maybe_whiteout = NULL; + rdd->ctx.pos = 0; + do { + rdd->count = 0; +@@ -238,6 +255,10 @@ static inline int ovl_dir_read(struct pa + if (err >= 0) + err = rdd->err; + } while (!err && rdd->count); ++ ++ if (!err && rdd->first_maybe_whiteout) ++ err = ovl_check_whiteouts(realpath->dentry, rdd); ++ + fput(realfile); + + return err; |