CVE-2014-8172 (C, published 2015-03-16T10:59Z)
Description: value: The filesystem implementation in the Linux kernel before 3.13 performs certain operations on lists of files with an inappropriate locking approach, which allows local users to cause a denial of service (soft lockup or system crash) via unspecified use of Asynchronous I/O (AIO) operations.
Severity: MEDIUM (CVSS v3 base score: )
Weakness: CWE-17 \u2014 DEPRECATED: Code
This entry has been deprecated.  It was originally used for organizing the Development View (CWE-699) and some other views, but it introduced unnecessary complexity and depth to the resulting tree.
Repository: https://github.com/torvalds/linux
Files changed:
fs/file_table.c
fs/internal.h
fs/open.c
fs/super.c
include/linux/fs.h
Commit message: get rid of s_files and files_lock

The only thing we need it for is alt-sysrq-r (emergency remount r/o)
and these days we can do just as well without going through the
list of files.

Signed-off-by: Al Viro <viro@zeniv.linux.org.uk>


Vulnerable code:
DEFINE_STATIC_LGLOCK(files_lglock);

	INIT_LIST_HEAD(&f->f_u.fu_list);
		file_sb_list_del(file);
		file_sb_list_del(file);
		file_sb_list_del(file);
static inline int file_list_cpu(struct file *file)
{
#ifdef CONFIG_SMP
	return file->f_sb_list_cpu;
#else
	return smp_processor_id();
#endif
}

/* helper for file_sb_list_add to reduce ifdefs */
static inline void __file_sb_list_add(struct file *file, struct super_block *sb)
{
	struct list_head *list;
#ifdef CONFIG_SMP
	int cpu;
	cpu = smp_processor_id();
	file->f_sb_list_cpu = cpu;
	list = per_cpu_ptr(sb->s_files, cpu);
#else
	list = &sb->s_files;
#endif
	list_add(&file->f_u.fu_list, list);
}

/**
 * file_sb_list_add - add a file to the sb's file list
 * @file: file to add
 * @sb: sb to add it to
 *
 * Use this function to associate a file with the superblock of the inode it
 * refers to.
 */
void file_sb_list_add(struct file *file, struct super_block *sb)
{
	if (likely(!(file->f_mode & FMODE_WRITE)))
		return;
	if (!S_ISREG(file_inode(file)->i_mode))
		return;
	lg_local_lock(&files_lglock);
	__file_sb_list_add(file, sb);
	lg_local_unlock(&files_lglock);
}

/**
 * file_sb_list_del - remove a file from the sb's file list
 * @file: file to remove
 * @sb: sb to remove it from
 *
 * Use this function to remove a file from its superblock.
 */
void file_sb_list_del(struct file *file)
{
	if (!list_empty(&file->f_u.fu_list)) {
		lg_local_lock_cpu(&files_lglock, file_list_cpu(file));
		list_del_init(&file->f_u.fu_list);
		lg_local_unlock_cpu(&files_lglock, file_list_cpu(file));
	}
}

#ifdef CONFIG_SMP

/*
 * These macros iterate all files on all CPUs for a given superblock.
 * files_lglock must be held globally.
 */
#define do_file_list_for_each_entry(__sb, __file)		\
{								\
	int i;							\
	for_each_possible_cpu(i) {				\
		struct list_head *list;				\
		list = per_cpu_ptr((__sb)->s_files, i);		\
		list_for_each_entry((__file), list, f_u.fu_list)

#define while_file_list_for_each_entry				\
	}							\
}

#else

#define do_file_list_for_each_entry(__sb, __file)		\
{								\
	struct list_head *list;					\
	list = &(sb)->s_files;					\
	list_for_each_entry((__file), list, f_u.fu_list)

#define while_file_list_for_each_entry				\
}

#endif

/**
 *	mark_files_ro - mark all files read-only
 *	@sb: superblock in question
 *
 *	All files are marked read-only.  We don't care about pending
 *	delete files so this should be used in 'force' mode only.
 */
void mark_files_ro(struct super_block *sb)
{
	struct file *f;

	lg_global_lock(&files_lglock);
	do_file_list_for_each_entry(sb, f) {
		if (!file_count(f))
			continue;
		if (!(f->f_mode & FMODE_WRITE))
			continue;
		spin_lock(&f->f_lock);
		f->f_mode &= ~FMODE_WRITE;
		spin_unlock(&f->f_lock);
		if (file_check_writeable(f) != 0)
			continue;
		__mnt_drop_write(f->f_path.mnt);
		file_release_write(f);
	} while_file_list_for_each_entry;
	lg_global_unlock(&files_lglock);
}

	lg_lock_init(&files_lglock, "files_lglock");
extern void file_sb_list_add(struct file *f, struct super_block *sb);
extern void file_sb_list_del(struct file *f);
extern void mark_files_ro(struct super_block *);
	file_sb_list_add(f, inode->i_sb);
	file_sb_list_del(f);
#ifdef CONFIG_SMP
	free_percpu(s->s_files);
#endif
#ifdef CONFIG_SMP
	s->s_files = alloc_percpu(struct list_head);
	if (!s->s_files)
		goto fail;
	for_each_possible_cpu(i)
		INIT_LIST_HEAD(per_cpu_ptr(s->s_files, i));
#else
	INIT_LIST_HEAD(&s->s_files);
#endif
			mark_files_ro(sb);
	/*
	 * fu_list becomes invalid after file_free is called and queued via
	 * fu_rcuhead for RCU freeing
	 */
		struct list_head	fu_list;
#ifdef CONFIG_SMP
	int			f_sb_list_cpu;
#endif
#ifdef CONFIG_SMP
	struct list_head __percpu *s_files;
#else
	struct list_head	s_files;
#endif

Fixed code:
			sb->s_readonly_remount = 1;
			smp_wmb();