diff mbox series

block: genhd: fix double kfree() in __alloc_disk_node()

Message ID 3999c511-cd27-1554-d3a6-4288c3eca298@i-love.sakura.ne.jp (mailing list archive)
State New, archived
Headers show
Series block: genhd: fix double kfree() in __alloc_disk_node() | expand

Commit Message

Tetsuo Handa Sept. 19, 2021, 2:44 p.m. UTC
syzbot is reporting use-after-free read at bdev_free_inode() [1], for
kfree() from __alloc_disk_node() is called before bdev_free_inode()
(which is called after RCU grace period) reads bdev->bd_disk and calls
kfree(bdev->bd_disk).

Fix use-after-free read followed by double kfree() problem
by explicitly resetting bdev->bd_disk to NULL before calling iput().

Link: https://syzkaller.appspot.com/bug?extid=8281086e8a6fbfbd952a [1]
Reported-by: syzbot <syzbot+8281086e8a6fbfbd952a@syzkaller.appspotmail.com>
Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
---
This patch is not tested due to lack of reproducer. Is this fix correct?

 block/bdev.c  | 1 +
 block/genhd.c | 1 +
 2 files changed, 2 insertions(+)

Comments

Christoph Hellwig Sept. 20, 2021, 6:40 a.m. UTC | #1
On Sun, Sep 19, 2021 at 11:44:29PM +0900, Tetsuo Handa wrote:
> syzbot is reporting use-after-free read at bdev_free_inode() [1], for
> kfree() from __alloc_disk_node() is called before bdev_free_inode()
> (which is called after RCU grace period) reads bdev->bd_disk and calls
> kfree(bdev->bd_disk).
> 
> Fix use-after-free read followed by double kfree() problem
> by explicitly resetting bdev->bd_disk to NULL before calling iput().
> 
> Link: https://syzkaller.appspot.com/bug?extid=8281086e8a6fbfbd952a [1]
> Reported-by: syzbot <syzbot+8281086e8a6fbfbd952a@syzkaller.appspotmail.com>
> Signed-off-by: Tetsuo Handa <penguin-kernel@I-love.SAKURA.ne.jp>
> ---
> This patch is not tested due to lack of reproducer. Is this fix correct?
> 
>  block/bdev.c  | 1 +
>  block/genhd.c | 1 +
>  2 files changed, 2 insertions(+)
> 
> diff --git a/block/bdev.c b/block/bdev.c
> index cf2780cb44a7..f6b8bac83bd8 100644
> --- a/block/bdev.c
> +++ b/block/bdev.c
> @@ -495,6 +495,7 @@ struct block_device *bdev_alloc(struct gendisk *disk, u8 partno)
>  	bdev->bd_inode = inode;
>  	bdev->bd_stats = alloc_percpu(struct disk_stats);
>  	if (!bdev->bd_stats) {
> +		bdev->bd_disk = NULL;
>  		iput(inode);
>  		return NULL;
>  	}

I was going to suggest to just move the bd_disk initialization after
the bd_stats allocations,  but iseems like we currently don't even
the zero the bdev on allocation.  So I suspect we should do that first
to avoid nasty surprises.
Tetsuo Handa Sept. 20, 2021, 8:02 a.m. UTC | #2
On 2021/09/20 15:40, Christoph Hellwig wrote:
> I was going to suggest to just move the bd_disk initialization after
> the bd_stats allocations,  but iseems like we currently don't even
> the zero the bdev on allocation.  So I suspect we should do that first
> to avoid nasty surprises.

Hmm? bdev_alloc_inode() zeros the bdev on allocation.
Are you talking about some other function?

static struct inode *bdev_alloc_inode(struct super_block *sb)
{
	struct bdev_inode *ei = kmem_cache_alloc(bdev_cachep, GFP_KERNEL);

	if (!ei)
		return NULL;
	memset(&ei->bdev, 0, sizeof(ei->bdev));
	return &ei->vfs_inode;
}
Christoph Hellwig Sept. 20, 2021, 8:05 a.m. UTC | #3
On Mon, Sep 20, 2021 at 05:02:19PM +0900, Tetsuo Handa wrote:
> On 2021/09/20 15:40, Christoph Hellwig wrote:
> > I was going to suggest to just move the bd_disk initialization after
> > the bd_stats allocations,  but iseems like we currently don't even
> > the zero the bdev on allocation.  So I suspect we should do that first
> > to avoid nasty surprises.
> 
> Hmm? bdev_alloc_inode() zeros the bdev on allocation.
> Are you talking about some other function?

Ah yes, we do.  Sorry, not enough coffee yet.  So in that case I think
you can simply move the bd_disk asignment later to simplify the
first hunk.
diff mbox series

Patch

diff --git a/block/bdev.c b/block/bdev.c
index cf2780cb44a7..f6b8bac83bd8 100644
--- a/block/bdev.c
+++ b/block/bdev.c
@@ -495,6 +495,7 @@  struct block_device *bdev_alloc(struct gendisk *disk, u8 partno)
 	bdev->bd_inode = inode;
 	bdev->bd_stats = alloc_percpu(struct disk_stats);
 	if (!bdev->bd_stats) {
+		bdev->bd_disk = NULL;
 		iput(inode);
 		return NULL;
 	}
diff --git a/block/genhd.c b/block/genhd.c
index 7b6e5e1cf956..496e8458c357 100644
--- a/block/genhd.c
+++ b/block/genhd.c
@@ -1268,6 +1268,7 @@  struct gendisk *__alloc_disk_node(struct request_queue *q, int node_id,
 
 out_destroy_part_tbl:
 	xa_destroy(&disk->part_tbl);
+	disk->part0->bd_disk = NULL;
 	iput(disk->part0->bd_inode);
 out_free_bdi:
 	bdi_put(disk->bdi);