Commit 09d967c6 authored by OGAWA Hirofumi's avatar OGAWA Hirofumi Committed by Linus Torvalds

[PATCH] Fix a race condition between ->i_mapping and iput()

This race became a cause of oops, and can reproduce by the following.

    while true; do
	dd if=/dev/zero of=/dev/.static/dev/hdg1 bs=512 count=1000 & sync
    done

This race condition was between __sync_single_inode() and iput().

          cpu0 (fs's inode)                 cpu1 (bdev's inode)
          -----------------                 -------------------
                                       close("/dev/hda2")
                                       [...]
__sync_single_inode()
   /* copy the bdev's ->i_mapping */
   mapping = inode->i_mapping;

                                       generic_forget_inode()
                                          bdev_clear_inode()
					     /* restre the fs's ->i_mapping */
				             inode->i_mapping = &inode->i_data;
				          /* bdev's inode was freed */
                                          destroy_inode(inode);

   if (wait) {
      /* dereference a freed bdev's mapping->host */
      filemap_fdatawait(mapping);  /* Oops */

Since __sync_single_inode() is only taking a ref-count of fs's inode, the
another process can be close() and freeing the bdev's inode while writing
fs's inode.  So, __sync_signle_inode() accesses the freed ->i_mapping,
oops.

This patch takes a ref-count on the bdev's inode for the fs's inode before
setting a ->i_mapping, and the clear_inode() of the fs's inode does iput() on
the bdev's inode.  So if the fs's inode is still living, bdev's inode
shouldn't be freed.
Signed-off-by: default avatarOGAWA Hirofumi <hirofumi@mail.parknet.co.jp>
Signed-off-by: default avatarAndrew Morton <akpm@osdl.org>
Signed-off-by: default avatarLinus Torvalds <torvalds@osdl.org>
parent 0e5b3781
...@@ -414,21 +414,31 @@ EXPORT_SYMBOL(bdput); ...@@ -414,21 +414,31 @@ EXPORT_SYMBOL(bdput);
static struct block_device *bd_acquire(struct inode *inode) static struct block_device *bd_acquire(struct inode *inode)
{ {
struct block_device *bdev; struct block_device *bdev;
spin_lock(&bdev_lock); spin_lock(&bdev_lock);
bdev = inode->i_bdev; bdev = inode->i_bdev;
if (bdev && igrab(bdev->bd_inode)) { if (bdev) {
atomic_inc(&bdev->bd_inode->i_count);
spin_unlock(&bdev_lock); spin_unlock(&bdev_lock);
return bdev; return bdev;
} }
spin_unlock(&bdev_lock); spin_unlock(&bdev_lock);
bdev = bdget(inode->i_rdev); bdev = bdget(inode->i_rdev);
if (bdev) { if (bdev) {
spin_lock(&bdev_lock); spin_lock(&bdev_lock);
if (inode->i_bdev) if (!inode->i_bdev) {
__bd_forget(inode); /*
* We take an additional bd_inode->i_count for inode,
* and it's released in clear_inode() of inode.
* So, we can access it via ->i_mapping always
* without igrab().
*/
atomic_inc(&bdev->bd_inode->i_count);
inode->i_bdev = bdev; inode->i_bdev = bdev;
inode->i_mapping = bdev->bd_inode->i_mapping; inode->i_mapping = bdev->bd_inode->i_mapping;
list_add(&inode->i_devices, &bdev->bd_inodes); list_add(&inode->i_devices, &bdev->bd_inodes);
}
spin_unlock(&bdev_lock); spin_unlock(&bdev_lock);
} }
return bdev; return bdev;
...@@ -438,10 +448,18 @@ static struct block_device *bd_acquire(struct inode *inode) ...@@ -438,10 +448,18 @@ static struct block_device *bd_acquire(struct inode *inode)
void bd_forget(struct inode *inode) void bd_forget(struct inode *inode)
{ {
struct block_device *bdev = NULL;
spin_lock(&bdev_lock); spin_lock(&bdev_lock);
if (inode->i_bdev) if (inode->i_bdev) {
if (inode->i_sb != blockdev_superblock)
bdev = inode->i_bdev;
__bd_forget(inode); __bd_forget(inode);
}
spin_unlock(&bdev_lock); spin_unlock(&bdev_lock);
if (bdev)
iput(bdev->bd_inode);
} }
int bd_claim(struct block_device *bdev, void *holder) int bd_claim(struct block_device *bdev, void *holder)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment