diff mbox

[2/4] jffs2: fix unbalanced locking

Message ID 52FC81E4.2020301@huawei.com
State RFC
Headers show

Commit Message

Zefan Li Feb. 13, 2014, 8:27 a.m. UTC
On 2014/2/13 14:48, Brian Norris wrote:
> Hi Li / Andrew,
> 
> On Wed, Feb 12, 2014 at 12:44:55PM -0800, Andrew Morton wrote:
>> From: Li Zefan <lizefan@huawei.com>
>> Subject: jffs2: fix unbalanced locking
>>
>> This was found by our internal debugging feature on runtime, but this bug
>> won't lead to deadlock, as the structure that this lock is embedded in is
>> freed on error.
> 
> Well, one of its callers frees it without unlocking it, but you're
> forgetting about one of its other callers, and in doing so, you are
> introducing a potential double-unlock instead!
> 

But I don't think I should be blamed here.

Without my patch, some of the error paths release f->sem but some don't,
so the potential double-unlock is already there.

> Look at
>   jffs2_iget()
>   |_ mutex_lock(&f->sem)
>   |_ jffs2_do_read_inode()
>   |  |_ jffs2_do_read_inode_internal()
>   |_ mutex_unlock(&f->sem)
> 
> jffs2_iget() already has the proper locking for f->sem, but with your
> patch, you're turning this into a double-unlock in the error case.
> 
> So unless I'm mistaken, I'll give a NAK to this patch. It's one of those
> patches generated by automated testing that has no practical value, but
> rather has the potential to cause more bugs.
> 
> BTW, the right way to handle lock balancing is to handle the unlocking
> at the same level where you do the locking. So I guess you're looking
> for the following patch instead, which is really not very useful because
> (as Li noted) the lock is freed immediately afterward anyway:
> 

Yeah, I do believe it's better to do locking/unlocking in the same level.
How about this:

[PATCH v2] jffs2: fix unbalanced locking

On runtime our internal debugging feature warned f->sem isn't unlocked
when returning to userspace. It's because f->sem isn't unlocked in
jffs2_do_crccheck_inode() on error, but this bug won't lead to deadlock,
as the structure that this lock is embedded in is freed immediately.

After looking into the code, I found in jffs2_do_read_inode_internal()
some error paths release f->sem but some won't, so this may lead to
double-unlock:

  jffs2_iget()
  |_ mutex_lock(&f->sem)
  |_ jffs2_do_read_inode()
  |  |_ jffs2_do_read_inode_internal()
  |     |_ mutex_unlock(&f->sem)
  |     |_ jffs2_do_clear_inode(c, f)
  |     |_ return ret
  |_ mutex_unlock(&f->sem)

This patch makes sure jffs2_do_read_inode_internal() never returns
with f->sem unlocked, so locking and unlocking are in the same level.

Cc: <stable@vger.kernel.org>
Signed-off-by: Li Zefan <lizefan@huawei.com>
---
 fs/jffs2/readinode.c | 53 ++++++++++++++++++++++++----------------------------
 1 file changed, 24 insertions(+), 29 deletions(-)
diff mbox

Patch

diff --git a/fs/jffs2/readinode.c b/fs/jffs2/readinode.c
index 386303d..6f22234 100644
--- a/fs/jffs2/readinode.c
+++ b/fs/jffs2/readinode.c
@@ -1203,18 +1203,16 @@  static int jffs2_do_read_inode_internal(struct jffs2_sb_info *c,
 		JFFS2_ERROR("failed to read from flash: error %d, %zd of %zd bytes read\n",
 			ret, retlen, sizeof(*latest_node));
 		/* FIXME: If this fails, there seems to be a memory leak. Find it. */
-		mutex_unlock(&f->sem);
-		jffs2_do_clear_inode(c, f);
-		return ret?ret:-EIO;
+		ret = ret ? ret : -EIO;
+		goto out;
 	}
 
 	crc = crc32(0, latest_node, sizeof(*latest_node)-8);
 	if (crc != je32_to_cpu(latest_node->node_crc)) {
 		JFFS2_ERROR("CRC failed for read_inode of inode %u at physical location 0x%x\n",
 			f->inocache->ino, ref_offset(rii.latest_ref));
-		mutex_unlock(&f->sem);
-		jffs2_do_clear_inode(c, f);
-		return -EIO;
+		ret = -EIO;
+		goto out;
 	}
 
 	switch(jemode_to_cpu(latest_node->mode) & S_IFMT) {
@@ -1251,16 +1249,14 @@  static int jffs2_do_read_inode_internal(struct jffs2_sb_info *c,
 			 * operation. */
 			uint32_t csize = je32_to_cpu(latest_node->csize);
 			if (csize > JFFS2_MAX_NAME_LEN) {
-				mutex_unlock(&f->sem);
-				jffs2_do_clear_inode(c, f);
-				return -ENAMETOOLONG;
+				ret = -ENAMETOOLONG;
+				goto out;
 			}
 			f->target = kmalloc(csize + 1, GFP_KERNEL);
 			if (!f->target) {
 				JFFS2_ERROR("can't allocate %u bytes of memory for the symlink target path cache\n", csize);
-				mutex_unlock(&f->sem);
-				jffs2_do_clear_inode(c, f);
-				return -ENOMEM;
+				ret = -ENOMEM;
+				goto out;
 			}
 
 			ret = jffs2_flash_read(c, ref_offset(rii.latest_ref) + sizeof(*latest_node),
@@ -1271,9 +1267,7 @@  static int jffs2_do_read_inode_internal(struct jffs2_sb_info *c,
 					ret = -EIO;
 				kfree(f->target);
 				f->target = NULL;
-				mutex_unlock(&f->sem);
-				jffs2_do_clear_inode(c, f);
-				return ret;
+				goto out;
 			}
 
 			f->target[csize] = '\0';
@@ -1289,25 +1283,22 @@  static int jffs2_do_read_inode_internal(struct jffs2_sb_info *c,
 		if (f->metadata) {
 			JFFS2_ERROR("Argh. Special inode #%u with mode 0%o had metadata node\n",
 			       f->inocache->ino, jemode_to_cpu(latest_node->mode));
-			mutex_unlock(&f->sem);
-			jffs2_do_clear_inode(c, f);
-			return -EIO;
+			ret = -EIO;
+			goto out;
 		}
 		if (!frag_first(&f->fragtree)) {
 			JFFS2_ERROR("Argh. Special inode #%u with mode 0%o has no fragments\n",
 			       f->inocache->ino, jemode_to_cpu(latest_node->mode));
-			mutex_unlock(&f->sem);
-			jffs2_do_clear_inode(c, f);
-			return -EIO;
+			ret = -EIO;
+			goto out;
 		}
 		/* ASSERT: f->fraglist != NULL */
 		if (frag_next(frag_first(&f->fragtree))) {
 			JFFS2_ERROR("Argh. Special inode #%u with mode 0x%x had more than one node\n",
 			       f->inocache->ino, jemode_to_cpu(latest_node->mode));
 			/* FIXME: Deal with it - check crc32, check for duplicate node, check times and discard the older one */
-			mutex_unlock(&f->sem);
-			jffs2_do_clear_inode(c, f);
-			return -EIO;
+			ret = -EIO;
+			goto out;
 		}
 		/* OK. We're happy */
 		f->metadata = frag_first(&f->fragtree)->node;
@@ -1317,8 +1308,13 @@  static int jffs2_do_read_inode_internal(struct jffs2_sb_info *c,
 	}
 	if (f->inocache->state == INO_STATE_READING)
 		jffs2_set_inocache_state(c, f->inocache, INO_STATE_PRESENT);
-
-	return 0;
+out:
+	if (ret) {
+		mutex_unlock(&f->sem);
+		jffs2_do_clear_inode(c, f);
+		mutex_lock(&f->sem);
+	}
+	return ret;
 }
 
 /* Scan the list of all nodes present for this ino, build map of versions, etc. */
@@ -1400,10 +1396,9 @@  int jffs2_do_crccheck_inode(struct jffs2_sb_info *c, struct jffs2_inode_cache *i
 	f->inocache = ic;
 
 	ret = jffs2_do_read_inode_internal(c, f, &n);
-	if (!ret) {
-		mutex_unlock(&f->sem);
+	mutex_unlock(&f->sem);
+	if (!ret)
 		jffs2_do_clear_inode(c, f);
-	}
 	jffs2_xattr_do_crccheck_inode(c, ic);
 	kfree (f);
 	return ret;