diff options
author | Ming Lei <ming.lei@canonical.com> | 2013-04-01 22:12:26 -0400 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-04-03 14:09:02 -0400 |
commit | f7db5e7660b122142410dcf36ba903c73d473250 (patch) | |
tree | 5385cf0537e4cad0a2896070c45f2b492a74f0ff /fs/sysfs | |
parent | 0f8b1a0204a12441cddbbf5be31e6338e0b8da1c (diff) |
sysfs: fix use after free in case of concurrent read/write and readdir
The inode->i_mutex isn't hold when updating filp->f_pos
in read()/write(), so the filp->f_pos might be read as
0 or 1 in readdir() when there is concurrent read()/write()
on this same file, then may cause use after free in readdir().
The bug can be reproduced with Li Zefan's test code on the
link:
https://patchwork.kernel.org/patch/2160771/
This patch fixes the use after free under this situation.
Cc: stable <stable@vger.kernel.org>
Reported-by: Li Zefan <lizefan@huawei.com>
Signed-off-by: Ming Lei <ming.lei@canonical.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
Diffstat (limited to 'fs/sysfs')
-rw-r--r-- | fs/sysfs/dir.c | 15 |
1 files changed, 11 insertions, 4 deletions
diff --git a/fs/sysfs/dir.c b/fs/sysfs/dir.c index c6f54abe9852..1bf016b5e88f 100644 --- a/fs/sysfs/dir.c +++ b/fs/sysfs/dir.c | |||
@@ -999,6 +999,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) | |||
999 | enum kobj_ns_type type; | 999 | enum kobj_ns_type type; |
1000 | const void *ns; | 1000 | const void *ns; |
1001 | ino_t ino; | 1001 | ino_t ino; |
1002 | loff_t off; | ||
1002 | 1003 | ||
1003 | type = sysfs_ns_type(parent_sd); | 1004 | type = sysfs_ns_type(parent_sd); |
1004 | ns = sysfs_info(dentry->d_sb)->ns[type]; | 1005 | ns = sysfs_info(dentry->d_sb)->ns[type]; |
@@ -1021,6 +1022,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) | |||
1021 | return 0; | 1022 | return 0; |
1022 | } | 1023 | } |
1023 | mutex_lock(&sysfs_mutex); | 1024 | mutex_lock(&sysfs_mutex); |
1025 | off = filp->f_pos; | ||
1024 | for (pos = sysfs_dir_pos(ns, parent_sd, filp->f_pos, pos); | 1026 | for (pos = sysfs_dir_pos(ns, parent_sd, filp->f_pos, pos); |
1025 | pos; | 1027 | pos; |
1026 | pos = sysfs_dir_next_pos(ns, parent_sd, filp->f_pos, pos)) { | 1028 | pos = sysfs_dir_next_pos(ns, parent_sd, filp->f_pos, pos)) { |
@@ -1032,19 +1034,24 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir) | |||
1032 | len = strlen(name); | 1034 | len = strlen(name); |
1033 | ino = pos->s_ino; | 1035 | ino = pos->s_ino; |
1034 | type = dt_type(pos); | 1036 | type = dt_type(pos); |
1035 | filp->f_pos = pos->s_hash; | 1037 | off = filp->f_pos = pos->s_hash; |
1036 | filp->private_data = sysfs_get(pos); | 1038 | filp->private_data = sysfs_get(pos); |
1037 | 1039 | ||
1038 | mutex_unlock(&sysfs_mutex); | 1040 | mutex_unlock(&sysfs_mutex); |
1039 | ret = filldir(dirent, name, len, filp->f_pos, ino, type); | 1041 | ret = filldir(dirent, name, len, off, ino, type); |
1040 | mutex_lock(&sysfs_mutex); | 1042 | mutex_lock(&sysfs_mutex); |
1041 | if (ret < 0) | 1043 | if (ret < 0) |
1042 | break; | 1044 | break; |
1043 | } | 1045 | } |
1044 | mutex_unlock(&sysfs_mutex); | 1046 | mutex_unlock(&sysfs_mutex); |
1045 | if ((filp->f_pos > 1) && !pos) { /* EOF */ | 1047 | |
1046 | filp->f_pos = INT_MAX; | 1048 | /* don't reference last entry if its refcount is dropped */ |
1049 | if (!pos) { | ||
1047 | filp->private_data = NULL; | 1050 | filp->private_data = NULL; |
1051 | |||
1052 | /* EOF and not changed as 0 or 1 in read/write path */ | ||
1053 | if (off == filp->f_pos && off > 1) | ||
1054 | filp->f_pos = INT_MAX; | ||
1048 | } | 1055 | } |
1049 | return 0; | 1056 | return 0; |
1050 | } | 1057 | } |