243 lines
7.8 KiB
Diff
243 lines
7.8 KiB
Diff
From: Al Viro <viro@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@zeniv.linux.org.uk>
|
|
---
|
|
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
|
|
|