aboutsummaryrefslogtreecommitdiffstats
path: root/fs/sysfs
diff options
context:
space:
mode:
authorMing Lei <ming.lei@canonical.com>2013-04-01 22:12:26 -0400
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>2013-04-03 14:09:02 -0400
commitf7db5e7660b122142410dcf36ba903c73d473250 (patch)
tree5385cf0537e4cad0a2896070c45f2b492a74f0ff /fs/sysfs
parent0f8b1a0204a12441cddbbf5be31e6338e0b8da1c (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.c15
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}