CVE-2012-4508 (C, published 2012-12-21T11:47Z)
Description: value: Race condition in fs/ext4/extents.c in the Linux kernel before 3.4.16 allows local users to obtain sensitive information from a deleted file by reading an extent that was not properly marked as uninitialized.
Severity: LOW (CVSS v3 base score: )
Weakness: CWE-362 \u2014 Concurrent Execution using Shared Resource with Improper Synchronization ('Race Condition')
The product contains a code sequence that can run concurrently with other code, and the code sequence requires temporary, exclusive access to a shared resource, but a timing window exists in which the shared resource can be modified by another code sequence that is operating concurrently.
Repository: https://github.com/torvalds/linux
Files changed:
fs/ext4/extents.c
Commit message: ext4: race-condition protection for ext4_convert_unwritten_extents_endio

We assumed that at the time we call ext4_convert_unwritten_extents_endio()
extent in question is fully inside [map.m_lblk, map->m_len] because
it was already split during submission.  But this may not be true due to
a race between writeback vs fallocate.

If extent in question is larger than requested we will split it again.
Special precautions should being done if zeroout required because
[map.m_lblk, map->m_len] already contains valid data.

Signed-off-by: Dmitry Monakhov <dmonakhov@openvz.org>
Signed-off-by: "Theodore Ts'o" <tytso@mit.edu>
Cc: stable@vger.kernel.org


Vulnerable code:
		err = ext4_ext_zeroout(inode, &orig_ex);
		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT ?
			      EXT4_EXT_MAY_ZEROOUT : 0;
		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT ?
			      EXT4_EXT_MAY_ZEROOUT : 0;

					      struct inode *inode,
					      struct ext4_ext_path *path)
		(unsigned long long)le32_to_cpu(ex->ee_block),
		ext4_ext_get_actual_len(ex));
		ret = ext4_convert_unwritten_extents_endio(handle, inode,

Fixed code:
#define EXT4_EXT_DATA_VALID1	0x8  /* first half contains valid data */
#define EXT4_EXT_DATA_VALID2	0x10 /* second half contains valid data */

	BUG_ON((split_flag & (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2)) ==
	       (EXT4_EXT_DATA_VALID1 | EXT4_EXT_DATA_VALID2));

		if (split_flag & (EXT4_EXT_DATA_VALID1|EXT4_EXT_DATA_VALID2)) {
			if (split_flag & EXT4_EXT_DATA_VALID1)
				err = ext4_ext_zeroout(inode, ex2);
			else
				err = ext4_ext_zeroout(inode, ex);
		} else
			err = ext4_ext_zeroout(inode, &orig_ex);

		split_flag1 = split_flag & EXT4_EXT_MAY_ZEROOUT;
		if (split_flag & EXT4_EXT_DATA_VALID2)
			split_flag1 |= EXT4_EXT_DATA_VALID1;
		split_flag1 = split_flag & (EXT4_EXT_MAY_ZEROOUT |
					    EXT4_EXT_DATA_VALID2);
	if (flags & EXT4_GET_BLOCKS_CONVERT)
		split_flag |= EXT4_EXT_DATA_VALID2;
						struct inode *inode,
						struct ext4_map_blocks *map,
						struct ext4_ext_path *path)
	ext4_lblk_t ee_block;
	unsigned int ee_len;
	ee_block = le32_to_cpu(ex->ee_block);
	ee_len = ext4_ext_get_actual_len(ex);
		  (unsigned long long)ee_block, ee_len);

	/* If extent is larger than requested then split is required */
	if (ee_block != map->m_lblk || ee_len > map->m_len) {
		err = ext4_split_unwritten_extents(handle, inode, map, path,
						   EXT4_GET_BLOCKS_CONVERT);
		if (err < 0)
			goto out;
		ext4_ext_drop_refs(path);
		path = ext4_ext_find_extent(inode, map->m_lblk, path);
		if (IS_ERR(path)) {
			err = PTR_ERR(path);
			goto out;
		}
		depth = ext_depth(inode);
		ex = path[depth].p_ext;
	}
		ret = ext4_convert_unwritten_extents_endio(handle, inode, map,