aboutsummaryrefslogtreecommitdiffstats
path: root/fs
diff options
context:
space:
mode:
authorMing Lei2013-04-01 21:12:26 -0500
committerGreg Kroah-Hartman2013-05-07 22:08:20 -0500
commitdda34083d85638e18a3d61701500c52405183e0c (patch)
treed0769d7e3e401d07f9e177ebcab0cb08386cc8b8 /fs
parent5d35a536a241865aeaf5f960783ce2c023fed2c3 (diff)
downloadkernel-omap-dda34083d85638e18a3d61701500c52405183e0c.tar.gz
kernel-omap-dda34083d85638e18a3d61701500c52405183e0c.tar.xz
kernel-omap-dda34083d85638e18a3d61701500c52405183e0c.zip
sysfs: fix use after free in case of concurrent read/write and readdir
commit f7db5e7660b122142410dcf36ba903c73d473250 upstream. 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. 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')
-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 1f8c823f7d8b..d924812dd16c 100644
--- a/fs/sysfs/dir.c
+++ b/fs/sysfs/dir.c
@@ -1012,6 +1012,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
1012 enum kobj_ns_type type; 1012 enum kobj_ns_type type;
1013 const void *ns; 1013 const void *ns;
1014 ino_t ino; 1014 ino_t ino;
1015 loff_t off;
1015 1016
1016 type = sysfs_ns_type(parent_sd); 1017 type = sysfs_ns_type(parent_sd);
1017 ns = sysfs_info(dentry->d_sb)->ns[type]; 1018 ns = sysfs_info(dentry->d_sb)->ns[type];
@@ -1034,6 +1035,7 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
1034 return 0; 1035 return 0;
1035 } 1036 }
1036 mutex_lock(&sysfs_mutex); 1037 mutex_lock(&sysfs_mutex);
1038 off = filp->f_pos;
1037 for (pos = sysfs_dir_pos(ns, parent_sd, filp->f_pos, pos); 1039 for (pos = sysfs_dir_pos(ns, parent_sd, filp->f_pos, pos);
1038 pos; 1040 pos;
1039 pos = sysfs_dir_next_pos(ns, parent_sd, filp->f_pos, pos)) { 1041 pos = sysfs_dir_next_pos(ns, parent_sd, filp->f_pos, pos)) {
@@ -1045,19 +1047,24 @@ static int sysfs_readdir(struct file * filp, void * dirent, filldir_t filldir)
1045 len = strlen(name); 1047 len = strlen(name);
1046 ino = pos->s_ino; 1048 ino = pos->s_ino;
1047 type = dt_type(pos); 1049 type = dt_type(pos);
1048 filp->f_pos = pos->s_hash; 1050 off = filp->f_pos = pos->s_hash;
1049 filp->private_data = sysfs_get(pos); 1051 filp->private_data = sysfs_get(pos);
1050 1052
1051 mutex_unlock(&sysfs_mutex); 1053 mutex_unlock(&sysfs_mutex);
1052 ret = filldir(dirent, name, len, filp->f_pos, ino, type); 1054 ret = filldir(dirent, name, len, off, ino, type);
1053 mutex_lock(&sysfs_mutex); 1055 mutex_lock(&sysfs_mutex);
1054 if (ret < 0) 1056 if (ret < 0)
1055 break; 1057 break;
1056 } 1058 }
1057 mutex_unlock(&sysfs_mutex); 1059 mutex_unlock(&sysfs_mutex);
1058 if ((filp->f_pos > 1) && !pos) { /* EOF */ 1060
1059 filp->f_pos = INT_MAX; 1061 /* don't reference last entry if its refcount is dropped */
1062 if (!pos) {
1060 filp->private_data = NULL; 1063 filp->private_data = NULL;
1064
1065 /* EOF and not changed as 0 or 1 in read/write path */
1066 if (off == filp->f_pos && off > 1)
1067 filp->f_pos = INT_MAX;
1061 } 1068 }
1062 return 0; 1069 return 0;
1063} 1070}