kernel: add linux 4.0 overlayfs locking fix by Miklos Szeredi
Signed-off-by: Felix Fietkau <nbd@openwrt.org> SVN-Revision: 46019
This commit is contained in:
		@@ -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;
 | 
				
			||||||
		Reference in New Issue
	
	Block a user