[linux] 01/01: dentry name snapshots (CVE-2017-7533)

debian-kernel at lists.debian.org debian-kernel at lists.debian.org
Thu Aug 3 17:32:42 UTC 2017


This is an automated email from the git hooks/post-receive script.

carnil pushed a commit to branch stretch-security
in repository linux.

commit 7c1804aa712bae44319bd481523c0500df16733b
Author: Salvatore Bonaccorso <carnil at debian.org>
Date:   Fri Jul 28 19:21:27 2017 +0200

    dentry name snapshots (CVE-2017-7533)
---
 debian/changelog                                   |   1 +
 .../patches/bugfix/all/dentry-name-snapshots.patch | 228 +++++++++++++++++++++
 debian/patches/series                              |   1 +
 3 files changed, 230 insertions(+)

diff --git a/debian/changelog b/debian/changelog
index cb4d572..c4f1c8a 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -12,6 +12,7 @@ linux (4.9.30-2+deb9u3) UNRELEASED; urgency=medium
   * xen-blkback: don't leak stack data via response ring (CVE-2017-10911)
   * mqueue: fix a use-after-free in sys_mq_notify() (CVE-2017-11176)
   * fs/exec.c: account for argv/envp pointers (CVE-2017-1000365)
+  * dentry name snapshots (CVE-2017-7533)
 
  -- Salvatore Bonaccorso <carnil at debian.org>  Wed, 26 Jul 2017 22:08:32 +0200
 
diff --git a/debian/patches/bugfix/all/dentry-name-snapshots.patch b/debian/patches/bugfix/all/dentry-name-snapshots.patch
new file mode 100644
index 0000000..ffe2a9f
--- /dev/null
+++ b/debian/patches/bugfix/all/dentry-name-snapshots.patch
@@ -0,0 +1,228 @@
+From: Al Viro <viro at zeniv.linux.org.uk>
+Date: Fri, 7 Jul 2017 14:51:19 -0400
+Subject: dentry name snapshots
+Origin: https://git.kernel.org/linus/49d31c2f389acfe83417083e1208422b4091cd9e
+Bug-Debian-Security: https://security-tracker.debian.org/tracker/CVE-2017-7533
+
+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 at zeniv.linux.org.uk>
+[carnil: backport 4.9: adjust context]
+---
+ fs/dcache.c              | 27 +++++++++++++++++++++++++++
+ fs/debugfs/inode.c       | 10 +++++-----
+ fs/namei.c               |  8 ++++----
+ fs/notify/fsnotify.c     |  8 ++++++--
+ include/linux/dcache.h   |  6 ++++++
+ include/linux/fsnotify.h | 31 -------------------------------
+ 6 files changed, 48 insertions(+), 42 deletions(-)
+
+--- a/fs/dcache.c
++++ b/fs/dcache.c
+@@ -277,6 +277,33 @@ static inline int dname_external(const s
+ 	return dentry->d_name.name != dentry->d_iname;
+ }
+ 
++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);
++
+ static inline void __d_set_inode_and_type(struct dentry *dentry,
+ 					  struct inode *inode,
+ 					  unsigned type_flags)
+--- a/fs/debugfs/inode.c
++++ b/fs/debugfs/inode.c
+@@ -730,7 +730,7 @@ struct dentry *debugfs_rename(struct den
+ {
+ 	int error;
+ 	struct dentry *dentry = NULL, *trap;
+-	const char *old_name;
++	struct name_snapshot old_name;
+ 
+ 	trap = lock_rename(new_dir, old_dir);
+ 	/* Source or destination directories don't exist? */
+@@ -745,19 +745,19 @@ struct dentry *debugfs_rename(struct den
+ 	if (IS_ERR(dentry) || dentry == trap || d_really_is_positive(dentry))
+ 		goto exit;
+ 
+-	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
++	take_dentry_name_snapshot(&old_name, old_dentry);
+ 
+ 	error = simple_rename(d_inode(old_dir), old_dentry, d_inode(new_dir),
+ 			      dentry, 0);
+ 	if (error) {
+-		fsnotify_oldname_free(old_name);
++		release_dentry_name_snapshot(&old_name);
+ 		goto exit;
+ 	}
+ 	d_move(old_dentry, dentry);
+-	fsnotify_move(d_inode(old_dir), d_inode(new_dir), old_name,
++	fsnotify_move(d_inode(old_dir), d_inode(new_dir), old_name.name,
+ 		d_is_dir(old_dentry),
+ 		NULL, old_dentry);
+-	fsnotify_oldname_free(old_name);
++	release_dentry_name_snapshot(&old_name);
+ 	unlock_rename(new_dir, old_dir);
+ 	dput(dentry);
+ 	return old_dentry;
+--- a/fs/namei.c
++++ b/fs/namei.c
+@@ -4336,11 +4336,11 @@ int vfs_rename(struct inode *old_dir, st
+ {
+ 	int error;
+ 	bool is_dir = d_is_dir(old_dentry);
+-	const unsigned char *old_name;
+ 	struct inode *source = old_dentry->d_inode;
+ 	struct inode *target = new_dentry->d_inode;
+ 	bool new_is_dir = false;
+ 	unsigned max_links = new_dir->i_sb->s_max_links;
++	struct name_snapshot old_name;
+ 
+ 	/*
+ 	 * Check source == target.
+@@ -4391,7 +4391,7 @@ int vfs_rename(struct inode *old_dir, st
+ 	if (error)
+ 		return error;
+ 
+-	old_name = fsnotify_oldname_init(old_dentry->d_name.name);
++	take_dentry_name_snapshot(&old_name, old_dentry);
+ 	dget(new_dentry);
+ 	if (!is_dir || (flags & RENAME_EXCHANGE))
+ 		lock_two_nondirectories(source, target);
+@@ -4446,14 +4446,14 @@ out:
+ 		inode_unlock(target);
+ 	dput(new_dentry);
+ 	if (!error) {
+-		fsnotify_move(old_dir, new_dir, old_name, is_dir,
++		fsnotify_move(old_dir, new_dir, old_name.name, is_dir,
+ 			      !(flags & RENAME_EXCHANGE) ? target : NULL, old_dentry);
+ 		if (flags & RENAME_EXCHANGE) {
+ 			fsnotify_move(new_dir, old_dir, old_dentry->d_name.name,
+ 				      new_is_dir, NULL, new_dentry);
+ 		}
+ 	}
+-	fsnotify_oldname_free(old_name);
++	release_dentry_name_snapshot(&old_name);
+ 
+ 	return error;
+ }
+--- a/fs/notify/fsnotify.c
++++ b/fs/notify/fsnotify.c
+@@ -104,16 +104,20 @@ int __fsnotify_parent(struct path *path,
+ 	if (unlikely(!fsnotify_inode_watches_children(p_inode)))
+ 		__fsnotify_update_child_dentry_flags(p_inode);
+ 	else if (p_inode->i_fsnotify_mask & mask) {
++		struct name_snapshot name;
++
+ 		/* we are notifying a parent so come up with the new mask which
+ 		 * specifies these are events which came from a child. */
+ 		mask |= FS_EVENT_ON_CHILD;
+ 
++		take_dentry_name_snapshot(&name, dentry);
+ 		if (path)
+ 			ret = fsnotify(p_inode, mask, path, FSNOTIFY_EVENT_PATH,
+-				       dentry->d_name.name, 0);
++				       name.name, 0);
+ 		else
+ 			ret = fsnotify(p_inode, mask, dentry->d_inode, FSNOTIFY_EVENT_INODE,
+-				       dentry->d_name.name, 0);
++				       name.name, 0);
++		release_dentry_name_snapshot(&name);
+ 	}
+ 
+ 	dput(parent);
+--- a/include/linux/dcache.h
++++ b/include/linux/dcache.h
+@@ -590,5 +590,11 @@ static inline struct inode *d_real_inode
+ 	return d_backing_inode(d_real((struct dentry *) dentry, NULL, 0));
+ }
+ 
++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 *);
+ 
+ #endif	/* __LINUX_DCACHE_H */
+--- a/include/linux/fsnotify.h
++++ b/include/linux/fsnotify.h
+@@ -293,35 +293,4 @@ static inline void fsnotify_change(struc
+ 	}
+ }
+ 
+-#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 */
+-
+ #endif	/* _LINUX_FS_NOTIFY_H */
diff --git a/debian/patches/series b/debian/patches/series
index 1711a03..6b78405 100644
--- a/debian/patches/series
+++ b/debian/patches/series
@@ -131,6 +131,7 @@ bugfix/all/drm-virtio-don-t-leak-bo-on-drm_gem_object_init-fail.patch
 bugfix/all/xen-blkback-don-t-leak-stack-data-via-response-ring.patch
 bugfix/all/mqueue-fix-a-use-after-free-in-sys_mq_notify.patch
 bugfix/all/fs-exec.c-account-for-argv-envp-pointers.patch
+bugfix/all/dentry-name-snapshots.patch
 
 # Fix exported symbol versions
 bugfix/ia64/revert-ia64-move-exports-to-definitions.patch

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/kernel/linux.git



More information about the Kernel-svn-changes mailing list