CVE-2015-8952 (C, published 2016-10-16T21:59Z)
Description: value: The mbcache feature in the ext2 and ext4 filesystem implementations in the Linux kernel before 4.6 mishandles xattr block caching, which allows local users to cause a denial of service (soft lockup) via filesystem operations in environments that use many attributes, as demonstrated by Ceph and Samba.
Severity: LOW (CVSS v3 base score: 5.5)
Weakness: CWE-19 \u2014 Data Processing Errors
Weaknesses in this category are typically found in functionality that processes data. Data processing is the manipulation of input to retrieve or save information.
Repository: https://github.com/torvalds/linux
Files changed:
fs/ext2/ext2.h
fs/ext2/super.c
fs/ext2/xattr.c
fs/ext2/xattr.h
Commit message: ext2: convert to mbcache2

The conversion is generally straightforward. We convert filesystem from
a global cache to per-fs one. Similarly to ext4 the tricky part is that
xattr block corresponding to found mbcache entry can get freed before we
get buffer lock for that block. So we have to check whether the entry is
still valid after getting the buffer lock.

Signed-off-by: Jan Kara <jack@suse.cz>
Signed-off-by: Theodore Ts'o <tytso@mit.edu>


Vulnerable code:
	ext2_xattr_put_super(sb);
	int err = init_ext2_xattr();
	if (err)
		return err;
		goto out1;
out1:
	exit_ext2_xattr();
	exit_ext2_xattr();
#include <linux/mbcache.h>
static int ext2_xattr_cache_insert(struct buffer_head *);
static struct mb_cache *ext2_xattr_cache;

	if (ext2_xattr_cache_insert(bh))
	if (ext2_xattr_cache_insert(bh))
	if (ext2_xattr_cache_insert(bh))
		struct mb_cache_entry *ce;

		ce = mb_cache_entry_get(ext2_xattr_cache, bh->b_bdev,
					bh->b_blocknr);
			if (ce)
				mb_cache_entry_free(ce);
			if (ce)
				mb_cache_entry_release(ce);
			ext2_xattr_cache_insert(new_bh);
			ext2_xattr_cache_insert(new_bh);
		struct mb_cache_entry *ce;

		ce = mb_cache_entry_get(ext2_xattr_cache, old_bh->b_bdev,
					old_bh->b_blocknr);
			if (ce)
				mb_cache_entry_free(ce);
			if (ce)
				mb_cache_entry_release(ce);
	struct mb_cache_entry *ce;
	ce = mb_cache_entry_get(ext2_xattr_cache, bh->b_bdev, bh->b_blocknr);
		if (ce)
			mb_cache_entry_free(ce);
		if (ce)
			mb_cache_entry_release(ce);
/*
 * ext2_xattr_put_super()
 *
 * This is called when a file system is unmounted.
 */
void
ext2_xattr_put_super(struct super_block *sb)
{
	mb_cache_shrink(sb->s_bdev);
}


ext2_xattr_cache_insert(struct buffer_head *bh)
	struct mb_cache_entry *ce;
	ce = mb_cache_entry_alloc(ext2_xattr_cache, GFP_NOFS);
	if (!ce)
		return -ENOMEM;
	error = mb_cache_entry_insert(ce, bh->b_bdev, bh->b_blocknr, hash);
		mb_cache_entry_free(ce);
	} else {
		ea_bdebug(bh, "inserting [%x] (%d cache entries)", (int)hash,
			  atomic_read(&ext2_xattr_cache->c_entry_count));
		mb_cache_entry_release(ce);
	}
	struct mb_cache_entry *ce;
	ce = mb_cache_entry_find_first(ext2_xattr_cache, inode->i_sb->s_bdev,
				       hash);
		if (IS_ERR(ce)) {
			if (PTR_ERR(ce) == -EAGAIN)
				goto again;
			break;
		}

			if (le32_to_cpu(HDR(bh)->h_refcount) >
				mb_cache_entry_release(ce);
		ce = mb_cache_entry_find_next(ce, inode->i_sb->s_bdev, hash);
int __init
init_ext2_xattr(void)
	ext2_xattr_cache = mb_cache_create("ext2_xattr", 6);
	if (!ext2_xattr_cache)
		return -ENOMEM;
	return 0;
void
exit_ext2_xattr(void)
	mb_cache_destroy(ext2_xattr_cache);
extern void ext2_xattr_put_super(struct super_block *);
extern int init_ext2_xattr(void);
extern void exit_ext2_xattr(void);
static inline void
ext2_xattr_put_super(struct super_block *sb)
{
}

static inline int
init_ext2_xattr(void)
{
	return 0;
}

static inline void
exit_ext2_xattr(void)

Fixed code:
struct mb2_cache;

	struct mb2_cache *s_mb_cache;
	if (sbi->s_mb_cache) {
		ext2_xattr_destroy_cache(sbi->s_mb_cache);
		sbi->s_mb_cache = NULL;
	}

#ifdef CONFIG_EXT2_FS_XATTR
	sbi->s_mb_cache = ext2_xattr_create_cache();
	if (!sbi->s_mb_cache) {
		ext2_msg(sb, KERN_ERR, "Failed to create an mb_cache");
		goto failed_mount3;
	}
#endif
	if (sbi->s_mb_cache)
		ext2_xattr_destroy_cache(sbi->s_mb_cache);
	int err;

		return err;
#include <linux/mbcache2.h>
static int ext2_xattr_cache_insert(struct mb2_cache *, struct buffer_head *);
	struct mb2_cache *ext2_mb_cache = EXT2_SB(inode->i_sb)->s_mb_cache;
	if (ext2_xattr_cache_insert(ext2_mb_cache, bh))
	if (ext2_xattr_cache_insert(ext2_mb_cache, bh))
	struct mb2_cache *ext2_mb_cache = EXT2_SB(inode->i_sb)->s_mb_cache;
	if (ext2_xattr_cache_insert(ext2_mb_cache, bh))
			__u32 hash = le32_to_cpu(header->h_hash);

			/*
			 * This must happen under buffer lock for
			 * ext2_xattr_set2() to reliably detect modified block
			 */
			mb2_cache_entry_delete_block(EXT2_SB(sb)->s_mb_cache,
						     hash, bh->b_blocknr);

	struct mb2_cache *ext2_mb_cache = EXT2_SB(sb)->s_mb_cache;
			ext2_xattr_cache_insert(ext2_mb_cache, new_bh);
			ext2_xattr_cache_insert(ext2_mb_cache, new_bh);
			__u32 hash = le32_to_cpu(HDR(old_bh)->h_hash);

			/*
			 * This must happen under buffer lock for
			 * ext2_xattr_set2() to reliably detect freed block
			 */
			mb2_cache_entry_delete_block(ext2_mb_cache,
						     hash, old_bh->b_blocknr);
		__u32 hash = le32_to_cpu(HDR(bh)->h_hash);

		/*
		 * This must happen under buffer lock for ext2_xattr_set2() to
		 * reliably detect freed block
		 */
		mb2_cache_entry_delete_block(EXT2_SB(inode->i_sb)->s_mb_cache,
					     hash, bh->b_blocknr);
ext2_xattr_cache_insert(struct mb2_cache *cache, struct buffer_head *bh)
	error = mb2_cache_entry_create(cache, GFP_NOFS, hash, bh->b_blocknr);
	} else
		ea_bdebug(bh, "inserting [%x]", (int)hash);
	struct mb2_cache_entry *ce;
	struct mb2_cache *ext2_mb_cache = EXT2_SB(inode->i_sb)->s_mb_cache;
	ce = mb2_cache_entry_find_first(ext2_mb_cache, hash);
			/*
			 * We have to be careful about races with freeing or
			 * rehashing of xattr block. Once we hold buffer lock
			 * xattr block's state is stable so we can check
			 * whether the block got freed / rehashed or not.
			 * Since we unhash mbcache entry under buffer lock when
			 * freeing / rehashing xattr block, checking whether
			 * entry is still hashed is reliable.
			 */
			if (hlist_bl_unhashed(&ce->e_hash_list)) {
				mb2_cache_entry_put(ext2_mb_cache, ce);
				unlock_buffer(bh);
				brelse(bh);
				goto again;
			} else if (le32_to_cpu(HDR(bh)->h_refcount) >
				mb2_cache_entry_touch(ext2_mb_cache, ce);
				mb2_cache_entry_put(ext2_mb_cache, ce);
		ce = mb2_cache_entry_find_next(ext2_mb_cache, ce);
#define HASH_BUCKET_BITS 10

struct mb2_cache *ext2_xattr_create_cache(void)
	return mb2_cache_create(HASH_BUCKET_BITS);
void ext2_xattr_destroy_cache(struct mb2_cache *cache)
	if (cache)
		mb2_cache_destroy(cache);
struct mb2_cache;

extern struct mb2_cache *ext2_xattr_create_cache(void);
extern void ext2_xattr_destroy_cache(struct mb2_cache *cache);
static inline void ext2_xattr_destroy_cache(struct mb2_cache *cache)