drivers: soc: qcom: rpm_stats: Add mutex lock for shared data

The buffer allocated in file open operations need to be
protected as there can be a possiblity of use-after-free
scenario.

Process A              B
        |              |
      open             |
        |              |
      read started     |
        |             close

Add mutex lock to protect the buffer to avoid this.

"msm_rpmstats_copy_stats" accesses the variable "pdata->read_idx"
without locking. The userspace can invoke the "read" call from
multiple threads which will call "msm_rpmstats_file_read" which
in turn calls "msm_rpmstats_copy_stats".

This can allow the statement "pdata->read_idx++" increment
"read_idx" beyond the limit ("prvdata->num_records") and call
"msm_rpmstats_read_register" with this value.

Also allow reading RPM stats information using sysfs nodes.

The stats are available at
	/sys/power/system_sleep/stats

Change-Id: I031f02bb2694a97ced86da0a9f54d0e434e4ad6d
Signed-off-by: Naresh Malladi <namall@codeaurora.org>
This commit is contained in:
Naresh Malladi 2017-06-05 21:45:45 +05:30
parent ac8832ae3c
commit 4fb4afa4ac
2 changed files with 73 additions and 28 deletions

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2012-2015, The Linux Foundation. All rights reserved.
/* Copyright (c) 2012-2015, 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -50,6 +50,8 @@
#define GET_FIELD(a) ((strnstr(#a, ".", 80) + 1))
static DEFINE_MUTEX(msm_rpm_master_stats_mutex);
struct msm_rpm_master_stats {
uint32_t active_cores;
uint32_t numshutdowns;
@ -80,9 +82,11 @@ int msm_rpm_master_stats_file_close(struct inode *inode,
{
struct msm_rpm_master_stats_private_data *private = file->private_data;
mutex_lock(&msm_rpm_master_stats_mutex);
if (private->reg_base)
iounmap(private->reg_base);
kfree(file->private_data);
mutex_unlock(&msm_rpm_master_stats_mutex);
return 0;
}
@ -95,14 +99,10 @@ static int msm_rpm_master_copy_stats(
static int master_cnt;
int count, j = 0;
char *buf;
static DEFINE_MUTEX(msm_rpm_master_stats_mutex);
mutex_lock(&msm_rpm_master_stats_mutex);
/* Iterate possible number of masters */
if (master_cnt > prvdata->num_masters - 1) {
master_cnt = 0;
mutex_unlock(&msm_rpm_master_stats_mutex);
return 0;
}
@ -254,7 +254,6 @@ static int msm_rpm_master_copy_stats(
}
master_cnt++;
mutex_unlock(&msm_rpm_master_stats_mutex);
return RPM_MASTERS_BUF_LEN - count;
}
@ -263,25 +262,36 @@ static ssize_t msm_rpm_master_stats_file_read(struct file *file,
{
struct msm_rpm_master_stats_private_data *prvdata;
struct msm_rpm_master_stats_platform_data *pdata;
ssize_t ret;
mutex_lock(&msm_rpm_master_stats_mutex);
prvdata = file->private_data;
if (!prvdata)
return -EINVAL;
if (!prvdata) {
ret = -EINVAL;
goto exit;
}
pdata = prvdata->platform_data;
if (!pdata)
return -EINVAL;
if (!pdata) {
ret = -EINVAL;
goto exit;
}
if (!bufu || count == 0)
return -EINVAL;
if (!bufu || count == 0) {
ret = -EINVAL;
goto exit;
}
if ((*ppos <= pdata->phys_size)) {
prvdata->len = msm_rpm_master_copy_stats(prvdata);
*ppos = 0;
}
return simple_read_from_buffer(bufu, count, ppos,
ret = simple_read_from_buffer(bufu, count, ppos,
prvdata->buf, prvdata->len);
exit:
mutex_unlock(&msm_rpm_master_stats_mutex);
return ret;
}
static int msm_rpm_master_stats_file_open(struct inode *inode,
@ -289,15 +299,20 @@ static int msm_rpm_master_stats_file_open(struct inode *inode,
{
struct msm_rpm_master_stats_private_data *prvdata;
struct msm_rpm_master_stats_platform_data *pdata;
int ret = 0;
mutex_lock(&msm_rpm_master_stats_mutex);
pdata = inode->i_private;
file->private_data =
kzalloc(sizeof(struct msm_rpm_master_stats_private_data),
GFP_KERNEL);
if (!file->private_data)
return -ENOMEM;
if (!file->private_data) {
ret = -ENOMEM;
goto exit;
}
prvdata = file->private_data;
prvdata->reg_base = ioremap(pdata->phys_addr_base,
@ -308,14 +323,17 @@ static int msm_rpm_master_stats_file_open(struct inode *inode,
pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n",
__func__, &pdata->phys_addr_base,
pdata->phys_size);
return -EBUSY;
ret = -EBUSY;
goto exit;
}
prvdata->len = 0;
prvdata->num_masters = pdata->num_masters;
prvdata->master_names = pdata->masters;
prvdata->platform_data = pdata;
return 0;
exit:
mutex_unlock(&msm_rpm_master_stats_mutex);
return ret;
}
static const struct file_operations msm_rpm_master_stats_fops = {

View File

@ -1,4 +1,4 @@
/* Copyright (c) 2011-2015, The Linux Foundation. All rights reserved.
/* Copyright (c) 2011-2015, 2017, The Linux Foundation. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 and
@ -31,6 +31,8 @@
#define GET_PDATA_OF_ATTR(attr) \
(container_of(attr, struct msm_rpmstats_kobj_attr, ka)->pd)
static DEFINE_MUTEX(rpm_stats_mutex);
enum {
ID_COUNTER,
ID_ACCUM_TIME_SCLK,
@ -218,6 +220,12 @@ static int msm_rpmstats_copy_stats(struct msm_rpmstats_private_data *pdata)
record.id = msm_rpmstats_read_register(pdata->reg_base,
pdata->read_idx, 1);
if (record.id >= ID_MAX) {
pr_err("%s: array out of bound error found.\n",
__func__);
return -EINVAL;
}
record.val = msm_rpmstats_read_register(pdata->reg_base,
pdata->read_idx, 2);
@ -240,13 +248,20 @@ static ssize_t msm_rpmstats_file_read(struct file *file, char __user *bufu,
size_t count, loff_t *ppos)
{
struct msm_rpmstats_private_data *prvdata;
ssize_t ret;
mutex_lock(&rpm_stats_mutex);
prvdata = file->private_data;
if (!prvdata)
return -EINVAL;
if (!prvdata) {
ret = -EINVAL;
goto exit;
}
if (!bufu || count == 0)
return -EINVAL;
if (!bufu || count == 0) {
ret = -EINVAL;
goto exit;
}
if (prvdata->platform_data->version == 1) {
if (!prvdata->num_records)
@ -263,22 +278,30 @@ static ssize_t msm_rpmstats_file_read(struct file *file, char __user *bufu,
*ppos = 0;
}
return simple_read_from_buffer(bufu, count, ppos,
ret = simple_read_from_buffer(bufu, count, ppos,
prvdata->buf, prvdata->len);
exit:
mutex_unlock(&rpm_stats_mutex);
return ret;
}
static int msm_rpmstats_file_open(struct inode *inode, struct file *file)
{
struct msm_rpmstats_private_data *prvdata;
struct msm_rpmstats_platform_data *pdata;
int ret = 0;
mutex_lock(&rpm_stats_mutex);
pdata = inode->i_private;
file->private_data =
kmalloc(sizeof(struct msm_rpmstats_private_data), GFP_KERNEL);
if (!file->private_data)
return -ENOMEM;
if (!file->private_data) {
ret = -ENOMEM;
goto exit;
}
prvdata = file->private_data;
prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base,
@ -289,24 +312,28 @@ static int msm_rpmstats_file_open(struct inode *inode, struct file *file)
pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n",
__func__, &pdata->phys_addr_base,
pdata->phys_size);
return -EBUSY;
ret = -EBUSY;
goto exit;
}
prvdata->read_idx = prvdata->num_records = prvdata->len = 0;
prvdata->platform_data = pdata;
if (pdata->version == 2)
prvdata->num_records = 2;
return 0;
exit:
mutex_unlock(&rpm_stats_mutex);
return ret;
}
static int msm_rpmstats_file_close(struct inode *inode, struct file *file)
{
struct msm_rpmstats_private_data *private = file->private_data;
mutex_lock(&rpm_stats_mutex);
if (private->reg_base)
iounmap(private->reg_base);
kfree(file->private_data);
mutex_unlock(&rpm_stats_mutex);
return 0;
}