CVE-2017-7533 (C, published 2017-08-05T16:29Z)
Description: value: Race condition in the fsnotify implementation in the Linux kernel through 4.12.4 allows local users to gain privileges or cause a denial of service (memory corruption) via a crafted application that leverages simultaneous execution of the inotify_handle_event and vfs_rename functions.
Severity: MEDIUM (CVSS v3 base score: 7)
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/dcache.c
fs/debugfs/inode.c
fs/namei.c
fs/notify/fsnotify.c
include/linux/dcache.h
include/linux/fsnotify.h
Commit message: dentry name snapshots

take_dentry_name_snapshot() takes a safe snapshot of dentry name;
if the name is a short one, it gets copied into caller-supplied
structure, otherwise an extra reference to external name is grabbed
(those are never modified).  In either case the pointer to stable
string is stored into the same structure.

dentry must be held by the caller of take_dentry_name_snapshot(),
but may be freely dropped afterwards - the snapshot will stay
until destroyed by release_dentry_name_snapshot().

Intended use:
	struct name_snapshot s;

	take_dentry_name_snapshot(&s, dentry);
	...
	access s.name
	...
	release_dentry_name_snapshot(&s);

Replaces fsnotify_oldname_...(), gets used in fsnotify to obtain the name
to pass down with event.

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


Vulnerable code:
	const char *old_name;
	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
		fsnotify_oldname_free(old_name);
	fsnotify_move(d_inode(old_dir), d_inode(new_dir), old_name,
	fsnotify_oldname_free(old_name);
	const unsigned char *old_name;
	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
		fsnotify_move(old_dir, new_dir, old_name, is_dir,
	fsnotify_oldname_free(old_name);
				       dentry->d_name.name, 0);
				       dentry->d_name.name, 0);
#if defined(CONFIG_FSNOTIFY)	/* notify helpers */

/*
 * fsnotify_oldname_init - save off the old filename before we change it
 */
static inline const unsigned char *fsnotify_oldname_init(const unsigned char *name)
{
	return kstrdup(name, GFP_KERNEL);
}

/*
 * fsnotify_oldname_free - free the name we got from fsnotify_oldname_init
 */
static inline void fsnotify_oldname_free(const unsigned char *old_name)
{
	kfree(old_name);
}

#else	/* CONFIG_FSNOTIFY */

static inline const char *fsnotify_oldname_init(const unsigned char *name)
{
	return NULL;
}

static inline void fsnotify_oldname_free(const unsigned char *old_name)
{
}

#endif	/*  CONFIG_FSNOTIFY */


Fixed code:
void take_dentry_name_snapshot(struct name_snapshot *name, struct dentry *dentry)
{
	spin_lock(&dentry->d_lock);
	if (unlikely(dname_external(dentry))) {
		struct external_name *p = external_name(dentry);
		atomic_inc(&p->u.count);
		spin_unlock(&dentry->d_lock);
		name->name = p->name;
	} else {
		memcpy(name->inline_name, dentry->d_iname, DNAME_INLINE_LEN);
		spin_unlock(&dentry->d_lock);
		name->name = name->inline_name;
	}
}
EXPORT_SYMBOL(take_dentry_name_snapshot);

void release_dentry_name_snapshot(struct name_snapshot *name)
{
	if (unlikely(name->name != name->inline_name)) {
		struct external_name *p;
		p = container_of(name->name, struct external_name, name[0]);
		if (unlikely(atomic_dec_and_test(&p->u.count)))
			kfree_rcu(p, u.head);
	}
}
EXPORT_SYMBOL(release_dentry_name_snapshot);

	struct name_snapshot old_name;
	take_dentry_name_snapshot(&old_name, old_dentry);
		release_dentry_name_snapshot(&old_name);
	fsnotify_move(d_inode(old_dir), d_inode(new_dir), old_name.name,
	release_dentry_name_snapshot(&old_name);
	struct name_snapshot old_name;
	take_dentry_name_snapshot(&old_name, old_dentry);
		fsnotify_move(old_dir, new_dir, old_name.name, is_dir,
	release_dentry_name_snapshot(&old_name);
		struct name_snapshot name;

		take_dentry_name_snapshot(&name, dentry);
				       name.name, 0);
				       name.name, 0);
		release_dentry_name_snapshot(&name);
struct name_snapshot {
	const char *name;
	char inline_name[DNAME_INLINE_LEN];
};
void take_dentry_name_snapshot(struct name_snapshot *, struct dentry *);
void release_dentry_name_snapshot(struct name_snapshot *);