CVE-2015-8324 (C, published 2016-05-02T10:59Z)
Description: value: The ext4 implementation in the Linux kernel before 2.6.34 does not properly track the initialization of certain data structures, which allows physically proximate attackers to cause a denial of service (NULL pointer dereference and panic) via a crafted USB device, related to the ext4_fill_super function.
Severity: MEDIUM (CVSS v3 base score: 4.6)
Weakness: NVD-CWE-Other \u2014 Other
NVD is only using a subset of CWE for mapping instead of the entire CWE, and the weakness type is not covered by that subset.
Repository: https://github.com/torvalds/linux
Files changed:
fs/ext4/ext4.h
fs/ext4/ext4_jbd2.h
fs/ext4/extents.c
fs/ext4/inode.c
fs/ext4/super.c
Commit message: ext4: use ext4_get_block_write in buffer write

Allocate uninitialized extent before ext4 buffer write and
convert the extent to initialized after io completes.
The purpose is to make sure an extent can only be marked
initialized after it has been written with new data so
we can safely drop the i_mutex lock in ext4 DIO read without
exposing stale data. This helps to improve multi-thread DIO
read performance on high-speed disks.

Skip the nobh and data=journal mount cases to make things simple for now.

Signed-off-by: Jiaying Zhang <jiayingz@google.com>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>


Vulnerable code:
	int			error;		/* I/O error code */
					 EXT4_GET_BLOCKS_IO_CREATE_EXT)
	if (ex && (flag != EXT4_GET_BLOCKS_PRE_IO)
	if (flag != EXT4_GET_BLOCKS_PRE_IO)
	if (flags == EXT4_GET_BLOCKS_PRE_IO) {
	if (flags == EXT4_GET_BLOCKS_CONVERT) {
		 * io_end structure was created for every async
		 * direct IO write to the middle of the file.
		 * To avoid unecessary convertion for every aio dio rewrite
		 * to the mid of file, here we flag the IO that is really
		 * need the convertion.
		if (flags == EXT4_GET_BLOCKS_PRE_IO) {
	ret = block_write_begin(file, mapping, pos, len, flags, pagep, fsdata,
				ext4_get_block);
	struct buffer_head *page_bufs;
	else
	handle_t *handle = NULL;
	if (max_blocks > DIO_MAX_BLOCKS)
		max_blocks = DIO_MAX_BLOCKS;
	dio_credits = ext4_chunk_trans_blocks(inode, max_blocks);
	handle = ext4_journal_start(inode, dio_credits);
	if (IS_ERR(handle)) {
		ret = PTR_ERR(handle);
		goto out;
	ext4_journal_stop(handle);
static void ext4_free_io_end(ext4_io_end_t *io)
{
	BUG_ON(!io);
	iput(io->inode);
	kfree(io);
}

	if (offset + size <= i_size_read(inode))
		ret = ext4_convert_unwritten_extents(inode, offset, size);

	ext4_io_end_t *io  = container_of(work, ext4_io_end_t, work);
	struct inode *inode = io->inode;
	int ret = 0;
	if (ret >= 0) {
		if (!list_empty(&io->list))
			list_del_init(&io->list);
		ext4_free_io_end(io);
	if (list_empty(&EXT4_I(inode)->i_completed_io_list))
	while (!list_empty(&EXT4_I(inode)->i_completed_io_list)){
		io = list_entry(EXT4_I(inode)->i_completed_io_list.next,
static ext4_io_end_t *ext4_init_io_end (struct inode *inode)
	io = kmalloc(sizeof(*io), GFP_NOFS);
		io->error = 0;
	list_add_tail(&io_end->list,
		 &EXT4_I(io_end->inode)->i_completed_io_list);
			iocb->private = ext4_init_io_end(inode);
		goto failed_mount4;
		goto failed_mount4;
			goto failed_mount4;


Fixed code:
	struct page		*page;		/* page struct for buffer write */
					 EXT4_GET_BLOCKS_CREATE_UNINIT_EXT)
	spinlock_t i_completed_io_lock;
#define EXT4_MOUNT_DIOREAD_NOLOCK	0x400000 /* Enable support for dio read nolocking */
/* BH_Uninit flag: blocks are allocated but uninitialized on disk */
enum ext4_state_bits {
	BH_Uninit	/* blocks are allocated but uninitialized on disk */
	  = BH_JBDPrivateStart,
};

BUFFER_FNS(Uninit, uninit)
TAS_BUFFER_FNS(Uninit, uninit)

/*
 * This function controls whether or not we should try to go down the
 * dioread_nolock code paths, which makes it safe to avoid taking
 * i_mutex for direct I/O reads.  This only works for extent-based
 * files, and it doesn't work for nobh or if data journaling is
 * enabled, since the dioread_nolock code uses b_private to pass
 * information back to the I/O completion handler, and this conflicts
 * with the jbd's use of b_private.
 */
static inline int ext4_should_dioread_nolock(struct inode *inode)
{
	if (!test_opt(inode->i_sb, DIOREAD_NOLOCK))
		return 0;
	if (test_opt(inode->i_sb, NOBH))
		return 0;
	if (!S_ISREG(inode->i_mode))
		return 0;
	if (!(EXT4_I(inode)->i_flags & EXT4_EXTENTS_FL))
		return 0;
	if (ext4_should_journal_data(inode))
		return 0;
	return 1;
}

	if (ex && !(flag & EXT4_GET_BLOCKS_PRE_IO)
	if (!(flag & EXT4_GET_BLOCKS_PRE_IO))
	if ((flags & EXT4_GET_BLOCKS_PRE_IO)) {
		if (ext4_should_dioread_nolock(inode))
			set_buffer_uninit(bh_result);
	if ((flags & EXT4_GET_BLOCKS_CONVERT)) {
		 * io_end structure was created for every IO write to an
		 * uninitialized extent. To avoid unecessary conversion,
		 * here we flag the IO that really needs the conversion.
		if ((flags & EXT4_GET_BLOCKS_PRE_IO)) {
		if (ext4_should_dioread_nolock(inode))
			set_buffer_uninit(bh_result);
#include <linux/kernel.h>
static int ext4_get_block_write(struct inode *inode, sector_t iblock,
		   struct buffer_head *bh_result, int create);
	if (ext4_should_dioread_nolock(inode))
		ret = block_write_begin(file, mapping, pos, len, flags, pagep,
				fsdata, ext4_get_block_write);
	else
		ret = block_write_begin(file, mapping, pos, len, flags, pagep,
				fsdata, ext4_get_block);
				if (buffer_uninit(exbh))
					set_buffer_uninit(bh);
	if (ext4_should_dioread_nolock(mpd->inode))
		get_blocks_flags |= EXT4_GET_BLOCKS_IO_CREATE_EXT;
static int ext4_set_bh_endio(struct buffer_head *bh, struct inode *inode);
static void ext4_end_io_buffer_write(struct buffer_head *bh, int uptodate);

	struct buffer_head *page_bufs = NULL;
	else if (page_bufs && buffer_uninit(page_bufs)) {
		ext4_set_bh_endio(page_bufs, inode);
		ret = block_write_full_page_endio(page, noalloc_get_block_write,
					    wbc, ext4_end_io_buffer_write);
	} else
static void ext4_free_io_end(ext4_io_end_t *io)
{
	BUG_ON(!io);
	if (io->page)
		put_page(io->page);
	iput(io->inode);
	kfree(io);
}

static void ext4_invalidatepage_free_endio(struct page *page, unsigned long offset)
{
	struct buffer_head *head, *bh;
	unsigned int curr_off = 0;

	if (!page_has_buffers(page))
		return;
	head = bh = page_buffers(page);
	do {
		if (offset <= curr_off && test_clear_buffer_uninit(bh)
					&& bh->b_private) {
			ext4_free_io_end(bh->b_private);
			bh->b_private = NULL;
			bh->b_end_io = NULL;
		}
		curr_off = curr_off + bh->b_size;
		bh = bh->b_this_page;
	} while (bh != head);
}

	/*
	 * free any io_end structure allocated for buffers to be discarded
	 */
	if (ext4_should_dioread_nolock(page->mapping->host))
		ext4_invalidatepage_free_endio(page, offset);
	handle_t *handle = ext4_journal_current_handle();
	int started = 0;
	if (!handle) {
		if (max_blocks > DIO_MAX_BLOCKS)
			max_blocks = DIO_MAX_BLOCKS;
		dio_credits = ext4_chunk_trans_blocks(inode, max_blocks);
		handle = ext4_journal_start(inode, dio_credits);
		if (IS_ERR(handle)) {
			ret = PTR_ERR(handle);
			goto out;
		}
		started = 1;

	if (started)
		ext4_journal_stop(handle);
	unsigned long flags;
	spin_lock_irqsave(&EXT4_I(inode)->i_completed_io_lock, flags);
	spin_unlock_irqrestore(&EXT4_I(inode)->i_completed_io_lock, flags);
	ret = ext4_convert_unwritten_extents(inode, offset, size);
	ext4_io_end_t		*io = container_of(work, ext4_io_end_t, work);
	struct inode		*inode = io->inode;
	struct ext4_inode_info	*ei = EXT4_I(inode);
	unsigned long		flags;
	int			ret;
	if (ret < 0) {
		mutex_unlock(&inode->i_mutex);
		return;

	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
	if (!list_empty(&io->list))
		list_del_init(&io->list);
	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
	ext4_free_io_end(io);
	struct ext4_inode_info *ei = EXT4_I(inode);
	unsigned long flags;
	if (list_empty(&ei->i_completed_io_list))
	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
	while (!list_empty(&ei->i_completed_io_list)){
		io = list_entry(ei->i_completed_io_list.next,
		spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
		spin_lock_irqsave(&ei->i_completed_io_lock, flags);
	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
static ext4_io_end_t *ext4_init_io_end (struct inode *inode, gfp_t flags)
	io = kmalloc(sizeof(*io), flags);
		io->page = NULL;
	unsigned long flags;
	struct ext4_inode_info *ei;
	io_end->flag = EXT4_IO_UNWRITTEN;
	ei = EXT4_I(io_end->inode);
	spin_lock_irqsave(&ei->i_completed_io_lock, flags);
	list_add_tail(&io_end->list, &ei->i_completed_io_list);
	spin_unlock_irqrestore(&ei->i_completed_io_lock, flags);
static void ext4_end_io_buffer_write(struct buffer_head *bh, int uptodate)
{
	ext4_io_end_t *io_end = bh->b_private;
	struct workqueue_struct *wq;
	struct inode *inode;
	unsigned long flags;

	if (!test_clear_buffer_uninit(bh) || !io_end)
		goto out;

	if (!(io_end->inode->i_sb->s_flags & MS_ACTIVE)) {
		printk("sb umounted, discard end_io request for inode %lu\n",
			io_end->inode->i_ino);
		ext4_free_io_end(io_end);
		goto out;
	}

	io_end->flag = EXT4_IO_UNWRITTEN;
	inode = io_end->inode;

	/* Add the io_end to per-inode completed io list*/
	spin_lock_irqsave(&EXT4_I(inode)->i_completed_io_lock, flags);
	list_add_tail(&io_end->list, &EXT4_I(inode)->i_completed_io_list);
	spin_unlock_irqrestore(&EXT4_I(inode)->i_completed_io_lock, flags);

	wq = EXT4_SB(inode->i_sb)->dio_unwritten_wq;
	/* queue the work to convert unwritten extents to written */
	queue_work(wq, &io_end->work);
out:
	bh->b_private = NULL;
	bh->b_end_io = NULL;
	clear_buffer_uninit(bh);
	end_buffer_async_write(bh, uptodate);
}

static int ext4_set_bh_endio(struct buffer_head *bh, struct inode *inode)
{
	ext4_io_end_t *io_end;
	struct page *page = bh->b_page;
	loff_t offset = (sector_t)page->index << PAGE_CACHE_SHIFT;
	size_t size = bh->b_size;

retry:
	io_end = ext4_init_io_end(inode, GFP_ATOMIC);
	if (!io_end) {
		if (printk_ratelimit())
			printk(KERN_WARNING "%s: allocation fail\n", __func__);
		schedule();
		goto retry;
	}
	io_end->offset = offset;
	io_end->size = size;
	/*
	 * We need to hold a reference to the page to make sure it
	 * doesn't get evicted before ext4_end_io_work() has a chance
	 * to convert the extent from written to unwritten.
	 */
	io_end->page = page;
	get_page(io_end->page);

	bh->b_private = io_end;
	bh->b_end_io = ext4_end_io_buffer_write;
	return 0;
}

			iocb->private = ext4_init_io_end(inode, GFP_NOFS);
	spin_lock_init(&ei->i_completed_io_lock);
	if (test_opt(sb, DIOREAD_NOLOCK))
		seq_puts(seq, ",dioread_nolock");

	Opt_dioread_nolock, Opt_dioread_lock,
	{Opt_dioread_nolock, "dioread_nolock"},
	{Opt_dioread_lock, "dioread_lock"},
		case Opt_dioread_nolock:
			set_opt(sbi->s_mount_opt, DIOREAD_NOLOCK);
			break;
		case Opt_dioread_lock:
			clear_opt(sbi->s_mount_opt, DIOREAD_NOLOCK);
			break;
		goto failed_mount_wq;
		goto failed_mount_wq;
			goto failed_mount_wq;
		if (test_opt(sb, DIOREAD_NOLOCK)) {
			ext4_msg(sb, KERN_WARNING, "dioread_nolock option is "
				"not supported with nobh mode");
			goto failed_mount_wq;
		}
	if (test_opt(sb, DIOREAD_NOLOCK)) {
		if (test_opt(sb, DATA_FLAGS) == EXT4_MOUNT_JOURNAL_DATA) {
			ext4_msg(sb, KERN_WARNING, "Ignoring dioread_nolock "
				"option - requested data journaling mode");
			clear_opt(sbi->s_mount_opt, DIOREAD_NOLOCK);
		}
		if (sb->s_blocksize < PAGE_SIZE) {
			ext4_msg(sb, KERN_WARNING, "Ignoring dioread_nolock "
				"option - block size is too small");
			clear_opt(sbi->s_mount_opt, DIOREAD_NOLOCK);
		}
	}