From b5081c50bcc119101beb307ff5950f964d042d14 Mon Sep 17 00:00:00 2001 From: Salvatore Bonaccorso Date: Thu, 3 Aug 2017 20:26:57 +0200 Subject: [PATCH] dentry name snapshots (CVE-2017-7533) --- debian/changelog | 3 + .../bugfix/all/dentry-name-snapshots.patch | 242 ++++++++++++++++++ debian/patches/series | 1 + 3 files changed, 246 insertions(+) create mode 100644 debian/patches/bugfix/all/dentry-name-snapshots.patch diff --git a/debian/changelog b/debian/changelog index 218bd1cce..40e7caefb 100644 --- a/debian/changelog +++ b/debian/changelog @@ -23,6 +23,9 @@ linux (4.12.3-1~exp1) UNRELEASED; urgency=medium * debian/control: Fix version in dependencies on arch-independent linux-headers-*-common* (Closes: #869511) + [ Salvatore Bonaccorso ] + * dentry name snapshots (CVE-2017-7533) + -- Ben Hutchings Tue, 18 Jul 2017 13:26:41 +0100 linux (4.12.2-1~exp1) experimental; urgency=medium 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 000000000..1d12db5d1 --- /dev/null +++ b/debian/patches/bugfix/all/dentry-name-snapshots.patch @@ -0,0 +1,242 @@ +From: Al Viro +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 +--- + 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(-) + +diff --git a/fs/dcache.c b/fs/dcache.c +index b85da8897ffa..831f3a9a8f05 100644 +--- a/fs/dcache.c ++++ b/fs/dcache.c +@@ -277,6 +277,33 @@ static inline int dname_external(const struct dentry *dentry) + 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) +diff --git a/fs/debugfs/inode.c b/fs/debugfs/inode.c +index e892ae7d89f8..acd3be2cc691 100644 +--- a/fs/debugfs/inode.c ++++ b/fs/debugfs/inode.c +@@ -766,7 +766,7 @@ struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry, + { + 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? */ +@@ -781,19 +781,19 @@ struct dentry *debugfs_rename(struct dentry *old_dir, struct dentry *old_dentry, + 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; +diff --git a/fs/namei.c b/fs/namei.c +index efe53a5d0737..c5588e837b15 100644 +--- a/fs/namei.c ++++ b/fs/namei.c +@@ -4362,11 +4362,11 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + { + 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; + + if (source == target) + return 0; +@@ -4413,7 +4413,7 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + 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); +@@ -4468,14 +4468,14 @@ int vfs_rename(struct inode *old_dir, struct dentry *old_dentry, + 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; + } +diff --git a/fs/notify/fsnotify.c b/fs/notify/fsnotify.c +index 01a9f0f007d4..0c4583b61717 100644 +--- a/fs/notify/fsnotify.c ++++ b/fs/notify/fsnotify.c +@@ -161,16 +161,20 @@ int __fsnotify_parent(const struct path *path, struct dentry *dentry, __u32 mask + 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); +diff --git a/include/linux/dcache.h b/include/linux/dcache.h +index d2e38dc6172c..025727bf6797 100644 +--- a/include/linux/dcache.h ++++ b/include/linux/dcache.h +@@ -591,5 +591,11 @@ static inline struct inode *d_real_inode(const struct dentry *dentry) + 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 */ +diff --git a/include/linux/fsnotify.h b/include/linux/fsnotify.h +index b43d3f5bd9ea..b78aa7ac77ce 100644 +--- a/include/linux/fsnotify.h ++++ b/include/linux/fsnotify.h +@@ -293,35 +293,4 @@ static inline void fsnotify_change(struct dentry *dentry, unsigned int ia_valid) + } + } + +-#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 */ +-- +2.11.0 + diff --git a/debian/patches/series b/debian/patches/series index 1073b33aa..aaf535a9b 100644 --- a/debian/patches/series +++ b/debian/patches/series @@ -117,6 +117,7 @@ features/all/lockdown/arm64-add-kernel-config-option-to-lock-down-when.patch # Security fixes debian/i386-686-pae-pci-set-pci-nobios-by-default.patch +bugfix/all/dentry-name-snapshots.patch # Fix exported symbol versions bugfix/alpha/alpha-restore-symbol-versions-for-symbols-exported-f.patch