aboutsummaryrefslogtreecommitdiff
path: root/fs
diff options
context:
space:
mode:
authorNick Piggin <npiggin@suse.de>2007-11-02 23:07:14 +0100
committerAdrian Bunk <bunk@kernel.org>2007-11-03 00:56:45 +0100
commit5ef24c6873e39aec57239e4623e6bc0182cc8954 (patch)
tree53236c435e62b318fbcc3c3c9cd74e9d710ba894 /fs
parent09e1acaea2074c5ae5d6d81f53f8c7f50c53593c (diff)
downloadlinux-linaro-stable-5ef24c6873e39aec57239e4623e6bc0182cc8954.tar.gz
buffer: memorder fix
unlock_buffer(), like unlock_page(), must not clear the lock without ensuring that the critical section is closed. Mingming later sent the same patch, saying: We are running SDET benchmark and saw double free issue for ext3 extended attributes block, which complains the same xattr block already being freed (in ext3_xattr_release_block()). The problem could also been triggered by multiple threads loop untar/rm a kernel tree. The race is caused by missing a memory barrier at unlock_buffer() before the lock bit being cleared, resulting in possible concurrent h_refcounter update. That causes a reference counter leak, then later leads to the double free that we have seen. Inside unlock_buffer(), there is a memory barrier is placed *after* the lock bit is being cleared, however, there is no memory barrier *before* the bit is cleared. On some arch the h_refcount update instruction and the clear bit instruction could be reordered, thus leave the critical section re-entered. The race is like this: For example, if the h_refcount is initialized as 1, cpu 0: cpu1 -------------------------------------- ----------------------------------- lock_buffer() /* test_and_set_bit */ clear_buffer_locked(bh); lock_buffer() /* test_and_set_bit */ h_refcount = h_refcount+1; /* = 2*/ h_refcount = h_refcount + 1; /*= 2 */ clear_buffer_locked(bh); .... ...... We lost a h_refcount here. We need a memory barrier before the buffer head lock bit being cleared to force the order of the two writes. Please apply. Signed-off-by: Nick Piggin <npiggin@suse.de> Signed-off-by: Adrian Bunk <bunk@kernel.org>
Diffstat (limited to 'fs')
-rw-r--r--fs/buffer.c1
1 files changed, 1 insertions, 0 deletions
diff --git a/fs/buffer.c b/fs/buffer.c
index 6d8b588a9961..f3fe5a0e126c 100644
--- a/fs/buffer.c
+++ b/fs/buffer.c
@@ -78,6 +78,7 @@ EXPORT_SYMBOL(__lock_buffer);
void fastcall unlock_buffer(struct buffer_head *bh)
{
+ smp_mb__before_clear_bit();
clear_buffer_locked(bh);
smp_mb__after_clear_bit();
wake_up_bit(&bh->b_state, BH_Lock);