kernel: add linux 4.0 overlayfs locking fix by Miklos Szeredi
authorFelix Fietkau <nbd@openwrt.org>
Wed, 17 Jun 2015 12:55:20 +0000 (12:55 +0000)
committerFelix Fietkau <nbd@openwrt.org>
Wed, 17 Jun 2015 12:55:20 +0000 (12:55 +0000)
Signed-off-by: Felix Fietkau <nbd@openwrt.org>
SVN-Revision: 46019

target/linux/generic/patches-4.0/140-overlayfs_readdir_locking_fix.patch [new file with mode: 0644]

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 (file)
index 0000000..67dff98
--- /dev/null
@@ -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;