From 6585c532bd958545bd6c53669264a60be9bd1bdf Mon Sep 17 00:00:00 2001
From: Felix Fietkau <nbd@openwrt.org>
Date: Wed, 17 Jun 2015 12:55:20 +0000
Subject: [PATCH] kernel: add linux 4.0 overlayfs locking fix by Miklos Szeredi

Signed-off-by: Felix Fietkau <nbd@openwrt.org>

SVN-Revision: 46019
---
 .../140-overlayfs_readdir_locking_fix.patch   | 148 ++++++++++++++++++
 1 file changed, 148 insertions(+)
 create mode 100644 target/linux/generic/patches-4.0/140-overlayfs_readdir_locking_fix.patch

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;
-- 
2.30.2