aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMingming Cao <cmm@us.ibm.com>2007-02-28 23:13:35 -0500
committerLinus Torvalds <torvalds@woody.linux-foundation.org>2007-03-01 17:53:38 -0500
commit8a2bfdcbfa441d8b0e5cb9c9a7f45f77f80da465 (patch)
tree10c90347c8eaf6dcad69b74198c535c2febd3387
parent1463fdbcc797dfcb8574ababbd39cf6205f6ed00 (diff)
[PATCH] ext[34]: EA block reference count racing fix
There are race issues around ext[34] xattr block release code. ext[34]_xattr_release_block() checks the reference count of xattr block (h_refcount) and frees that xattr block if it is the last one reference it. Unlike ext2, the check of this counter is unprotected by any lock. ext[34]_xattr_release_block() will free the mb_cache entry before freeing that xattr block. There is a small window between the check for the re h_refcount ==1 and the call to mb_cache_entry_free(). During this small window another inode might find this xattr block from the mbcache and reuse it, racing a refcount updates. The xattr block will later be freed by the first inode without notice other inode is still use it. Later if that block is reallocated as a datablock for other file, then more serious problem might happen. We need put a lock around places checking the refount as well to avoid racing issue. Another place need this kind of protection is in ext3_xattr_block_set(), where it will modify the xattr block content in- the-fly if the refcount is 1 (means it's the only inode reference it). This will also fix another issue: the xattr block may not get freed at all if no lock is to protect the refcount check at the release time. It is possible that the last two inodes could release the shared xattr block at the same time. But both of them think they are not the last one so only decreased the h_refcount without freeing xattr block at all. We need to call lock_buffer() after ext3_journal_get_write_access() to avoid deadlock (because the later will call lock_buffer()/unlock_buffer () as well). Signed-off-by: Mingming Cao <cmm@us.ibm.com> Cc: Andreas Gruenbacher <agruen@suse.de> Cc: <linux-ext4@vger.kernel.org> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
-rw-r--r--fs/ext3/xattr.c42
-rw-r--r--fs/ext4/xattr.c41
2 files changed, 51 insertions, 32 deletions
diff --git a/fs/ext3/xattr.c b/fs/ext3/xattr.c
index 99857a400f4b..12f7dda1232c 100644
--- a/fs/ext3/xattr.c
+++ b/fs/ext3/xattr.c
@@ -475,8 +475,15 @@ ext3_xattr_release_block(handle_t *handle, struct inode *inode,
475 struct buffer_head *bh) 475 struct buffer_head *bh)
476{ 476{
477 struct mb_cache_entry *ce = NULL; 477 struct mb_cache_entry *ce = NULL;
478 int error = 0;
478 479
479 ce = mb_cache_entry_get(ext3_xattr_cache, bh->b_bdev, bh->b_blocknr); 480 ce = mb_cache_entry_get(ext3_xattr_cache, bh->b_bdev, bh->b_blocknr);
481 error = ext3_journal_get_write_access(handle, bh);
482 if (error)
483 goto out;
484
485 lock_buffer(bh);
486
480 if (BHDR(bh)->h_refcount == cpu_to_le32(1)) { 487 if (BHDR(bh)->h_refcount == cpu_to_le32(1)) {
481 ea_bdebug(bh, "refcount now=0; freeing"); 488 ea_bdebug(bh, "refcount now=0; freeing");
482 if (ce) 489 if (ce)
@@ -485,21 +492,20 @@ ext3_xattr_release_block(handle_t *handle, struct inode *inode,
485 get_bh(bh); 492 get_bh(bh);
486 ext3_forget(handle, 1, inode, bh, bh->b_blocknr); 493 ext3_forget(handle, 1, inode, bh, bh->b_blocknr);
487 } else { 494 } else {
488 if (ext3_journal_get_write_access(handle, bh) == 0) { 495 BHDR(bh)->h_refcount = cpu_to_le32(
489 lock_buffer(bh);
490 BHDR(bh)->h_refcount = cpu_to_le32(
491 le32_to_cpu(BHDR(bh)->h_refcount) - 1); 496 le32_to_cpu(BHDR(bh)->h_refcount) - 1);
492 ext3_journal_dirty_metadata(handle, bh); 497 error = ext3_journal_dirty_metadata(handle, bh);
493 if (IS_SYNC(inode)) 498 handle->h_sync = 1;
494 handle->h_sync = 1; 499 DQUOT_FREE_BLOCK(inode, 1);
495 DQUOT_FREE_BLOCK(inode, 1); 500 ea_bdebug(bh, "refcount now=%d; releasing",
496 unlock_buffer(bh); 501 le32_to_cpu(BHDR(bh)->h_refcount));
497 ea_bdebug(bh, "refcount now=%d; releasing",
498 le32_to_cpu(BHDR(bh)->h_refcount));
499 }
500 if (ce) 502 if (ce)
501 mb_cache_entry_release(ce); 503 mb_cache_entry_release(ce);
502 } 504 }
505 unlock_buffer(bh);
506out:
507 ext3_std_error(inode->i_sb, error);
508 return;
503} 509}
504 510
505struct ext3_xattr_info { 511struct ext3_xattr_info {
@@ -675,7 +681,7 @@ ext3_xattr_block_set(handle_t *handle, struct inode *inode,
675 struct buffer_head *new_bh = NULL; 681 struct buffer_head *new_bh = NULL;
676 struct ext3_xattr_search *s = &bs->s; 682 struct ext3_xattr_search *s = &bs->s;
677 struct mb_cache_entry *ce = NULL; 683 struct mb_cache_entry *ce = NULL;
678 int error; 684 int error = 0;
679 685
680#define header(x) ((struct ext3_xattr_header *)(x)) 686#define header(x) ((struct ext3_xattr_header *)(x))
681 687
@@ -684,16 +690,17 @@ ext3_xattr_block_set(handle_t *handle, struct inode *inode,
684 if (s->base) { 690 if (s->base) {
685 ce = mb_cache_entry_get(ext3_xattr_cache, bs->bh->b_bdev, 691 ce = mb_cache_entry_get(ext3_xattr_cache, bs->bh->b_bdev,
686 bs->bh->b_blocknr); 692 bs->bh->b_blocknr);
693 error = ext3_journal_get_write_access(handle, bs->bh);
694 if (error)
695 goto cleanup;
696 lock_buffer(bs->bh);
697
687 if (header(s->base)->h_refcount == cpu_to_le32(1)) { 698 if (header(s->base)->h_refcount == cpu_to_le32(1)) {
688 if (ce) { 699 if (ce) {
689 mb_cache_entry_free(ce); 700 mb_cache_entry_free(ce);
690 ce = NULL; 701 ce = NULL;
691 } 702 }
692 ea_bdebug(bs->bh, "modifying in-place"); 703 ea_bdebug(bs->bh, "modifying in-place");
693 error = ext3_journal_get_write_access(handle, bs->bh);
694 if (error)
695 goto cleanup;
696 lock_buffer(bs->bh);
697 error = ext3_xattr_set_entry(i, s); 704 error = ext3_xattr_set_entry(i, s);
698 if (!error) { 705 if (!error) {
699 if (!IS_LAST_ENTRY(s->first)) 706 if (!IS_LAST_ENTRY(s->first))
@@ -713,6 +720,9 @@ ext3_xattr_block_set(handle_t *handle, struct inode *inode,
713 } else { 720 } else {
714 int offset = (char *)s->here - bs->bh->b_data; 721 int offset = (char *)s->here - bs->bh->b_data;
715 722
723 unlock_buffer(bs->bh);
724 journal_release_buffer(handle, bs->bh);
725
716 if (ce) { 726 if (ce) {
717 mb_cache_entry_release(ce); 727 mb_cache_entry_release(ce);
718 ce = NULL; 728 ce = NULL;
diff --git a/fs/ext4/xattr.c b/fs/ext4/xattr.c
index dc969c357aa1..e832e96095b3 100644
--- a/fs/ext4/xattr.c
+++ b/fs/ext4/xattr.c
@@ -475,8 +475,14 @@ ext4_xattr_release_block(handle_t *handle, struct inode *inode,
475 struct buffer_head *bh) 475 struct buffer_head *bh)
476{ 476{
477 struct mb_cache_entry *ce = NULL; 477 struct mb_cache_entry *ce = NULL;
478 int error = 0;
478 479
479 ce = mb_cache_entry_get(ext4_xattr_cache, bh->b_bdev, bh->b_blocknr); 480 ce = mb_cache_entry_get(ext4_xattr_cache, bh->b_bdev, bh->b_blocknr);
481 error = ext4_journal_get_write_access(handle, bh);
482 if (error)
483 goto out;
484
485 lock_buffer(bh);
480 if (BHDR(bh)->h_refcount == cpu_to_le32(1)) { 486 if (BHDR(bh)->h_refcount == cpu_to_le32(1)) {
481 ea_bdebug(bh, "refcount now=0; freeing"); 487 ea_bdebug(bh, "refcount now=0; freeing");
482 if (ce) 488 if (ce)
@@ -485,21 +491,21 @@ ext4_xattr_release_block(handle_t *handle, struct inode *inode,
485 get_bh(bh); 491 get_bh(bh);
486 ext4_forget(handle, 1, inode, bh, bh->b_blocknr); 492 ext4_forget(handle, 1, inode, bh, bh->b_blocknr);
487 } else { 493 } else {
488 if (ext4_journal_get_write_access(handle, bh) == 0) { 494 BHDR(bh)->h_refcount = cpu_to_le32(
489 lock_buffer(bh);
490 BHDR(bh)->h_refcount = cpu_to_le32(
491 le32_to_cpu(BHDR(bh)->h_refcount) - 1); 495 le32_to_cpu(BHDR(bh)->h_refcount) - 1);
492 ext4_journal_dirty_metadata(handle, bh); 496 error = ext4_journal_dirty_metadata(handle, bh);
493 if (IS_SYNC(inode)) 497 if (IS_SYNC(inode))
494 handle->h_sync = 1; 498 handle->h_sync = 1;
495 DQUOT_FREE_BLOCK(inode, 1); 499 DQUOT_FREE_BLOCK(inode, 1);
496 unlock_buffer(bh); 500 ea_bdebug(bh, "refcount now=%d; releasing",
497 ea_bdebug(bh, "refcount now=%d; releasing", 501 le32_to_cpu(BHDR(bh)->h_refcount));
498 le32_to_cpu(BHDR(bh)->h_refcount));
499 }
500 if (ce) 502 if (ce)
501 mb_cache_entry_release(ce); 503 mb_cache_entry_release(ce);
502 } 504 }
505 unlock_buffer(bh);
506out:
507 ext4_std_error(inode->i_sb, error);
508 return;
503} 509}
504 510
505struct ext4_xattr_info { 511struct ext4_xattr_info {
@@ -675,7 +681,7 @@ ext4_xattr_block_set(handle_t *handle, struct inode *inode,
675 struct buffer_head *new_bh = NULL; 681 struct buffer_head *new_bh = NULL;
676 struct ext4_xattr_search *s = &bs->s; 682 struct ext4_xattr_search *s = &bs->s;
677 struct mb_cache_entry *ce = NULL; 683 struct mb_cache_entry *ce = NULL;
678 int error; 684 int error = 0;
679 685
680#define header(x) ((struct ext4_xattr_header *)(x)) 686#define header(x) ((struct ext4_xattr_header *)(x))
681 687
@@ -684,16 +690,17 @@ ext4_xattr_block_set(handle_t *handle, struct inode *inode,
684 if (s->base) { 690 if (s->base) {
685 ce = mb_cache_entry_get(ext4_xattr_cache, bs->bh->b_bdev, 691 ce = mb_cache_entry_get(ext4_xattr_cache, bs->bh->b_bdev,
686 bs->bh->b_blocknr); 692 bs->bh->b_blocknr);
693 error = ext4_journal_get_write_access(handle, bs->bh);
694 if (error)
695 goto cleanup;
696 lock_buffer(bs->bh);
697
687 if (header(s->base)->h_refcount == cpu_to_le32(1)) { 698 if (header(s->base)->h_refcount == cpu_to_le32(1)) {
688 if (ce) { 699 if (ce) {
689 mb_cache_entry_free(ce); 700 mb_cache_entry_free(ce);
690 ce = NULL; 701 ce = NULL;
691 } 702 }
692 ea_bdebug(bs->bh, "modifying in-place"); 703 ea_bdebug(bs->bh, "modifying in-place");
693 error = ext4_journal_get_write_access(handle, bs->bh);
694 if (error)
695 goto cleanup;
696 lock_buffer(bs->bh);
697 error = ext4_xattr_set_entry(i, s); 704 error = ext4_xattr_set_entry(i, s);
698 if (!error) { 705 if (!error) {
699 if (!IS_LAST_ENTRY(s->first)) 706 if (!IS_LAST_ENTRY(s->first))
@@ -713,6 +720,8 @@ ext4_xattr_block_set(handle_t *handle, struct inode *inode,
713 } else { 720 } else {
714 int offset = (char *)s->here - bs->bh->b_data; 721 int offset = (char *)s->here - bs->bh->b_data;
715 722
723 unlock_buffer(bs->bh);
724 jbd2_journal_release_buffer(handle, bs->bh);
716 if (ce) { 725 if (ce) {
717 mb_cache_entry_release(ce); 726 mb_cache_entry_release(ce);
718 ce = NULL; 727 ce = NULL;