OSDN Git Service

wcnss: fix the potential memory leak and heap overflow
authorSarada Prasanna Garnayak <sgarna@codeaurora.org>
Mon, 17 Apr 2017 08:59:57 +0000 (14:29 +0530)
committerGerrit - the friendly Code Review server <code-review@localhost>
Fri, 21 Apr 2017 06:04:47 +0000 (23:04 -0700)
The wcnss platform driver update the wlan calibration data
by the user space wlan daemon. The wlan user space daemon store
the updated wlan calibration data reported by wlan firmware in
user space and write it back to the wcnss platform calibration
data buffer for the calibration data download and update.

During the wlan calibration data store and retrieve operation
there are some potential race condition which leads to memory leak
and buffer overflow during the context switch.

Fix the above issue by adding protection code and avoid usage of
global pointer during the device file read and write operation.

CRs-Fixed: 2015858
Change-Id: Ib5b57eb86dcb4e6ed799b5222d06396eaabfaad3
Signed-off-by: Sarada Prasanna Garnayak <sgarna@codeaurora.org>
drivers/net/wireless/wcnss/wcnss_wlan.c

index 9db2871..450b7ad 100644 (file)
@@ -398,7 +398,6 @@ static struct {
        int     user_cal_available;
        u32     user_cal_rcvd;
        int     user_cal_exp_size;
-       int     device_opened;
        int     iris_xo_mode_set;
        int     fw_vbatt_state;
        char    wlan_nv_macAddr[WLAN_MAC_ADDR_SIZE];
@@ -3265,14 +3264,6 @@ static int wcnss_node_open(struct inode *inode, struct file *file)
                        return -EFAULT;
        }
 
-       mutex_lock(&penv->dev_lock);
-       penv->user_cal_rcvd = 0;
-       penv->user_cal_read = 0;
-       penv->user_cal_available = false;
-       penv->user_cal_data = NULL;
-       penv->device_opened = 1;
-       mutex_unlock(&penv->dev_lock);
-
        return rc;
 }
 
@@ -3281,7 +3272,7 @@ static ssize_t wcnss_wlan_read(struct file *fp, char __user
 {
        int rc = 0;
 
-       if (!penv || !penv->device_opened)
+       if (!penv)
                return -EFAULT;
 
        rc = wait_event_interruptible(penv->read_wait, penv->fw_cal_rcvd
@@ -3318,55 +3309,66 @@ static ssize_t wcnss_wlan_write(struct file *fp, const char __user
                        *user_buffer, size_t count, loff_t *position)
 {
        int rc = 0;
-       u32 size = 0;
+       char *cal_data = NULL;
 
-       if (!penv || !penv->device_opened || penv->user_cal_available)
+       if (!penv || penv->user_cal_available)
                return -EFAULT;
 
-       if (penv->user_cal_rcvd == 0 && count >= 4
-                       && !penv->user_cal_data) {
-               rc = copy_from_user((void *)&size, user_buffer, 4);
-               if (!size || size > MAX_CALIBRATED_DATA_SIZE) {
-                       pr_err(DEVICE " invalid size to write %d\n", size);
+       if (!penv->user_cal_rcvd && count >= 4 && !penv->user_cal_exp_size) {
+               mutex_lock(&penv->dev_lock);
+               rc = copy_from_user((void *)&penv->user_cal_exp_size,
+                                   user_buffer, 4);
+               if (!penv->user_cal_exp_size ||
+                   penv->user_cal_exp_size > MAX_CALIBRATED_DATA_SIZE) {
+                       pr_err(DEVICE " invalid size to write %d\n",
+                              penv->user_cal_exp_size);
+                       penv->user_cal_exp_size = 0;
+                       mutex_unlock(&penv->dev_lock);
                        return -EFAULT;
                }
-
-               rc += count;
-               count -= 4;
-               penv->user_cal_exp_size =  size;
-               penv->user_cal_data = kmalloc(size, GFP_KERNEL);
-               if (penv->user_cal_data == NULL) {
-                       pr_err(DEVICE " no memory to write\n");
-                       return -ENOMEM;
-               }
-               if (0 == count)
-                       goto exit;
-
-       } else if (penv->user_cal_rcvd == 0 && count < 4)
+               mutex_unlock(&penv->dev_lock);
+               return count;
+       } else if (!penv->user_cal_rcvd && count < 4) {
                return -EFAULT;
+       }
 
+       mutex_lock(&penv->dev_lock);
        if ((UINT32_MAX - count < penv->user_cal_rcvd) ||
                (penv->user_cal_exp_size < count + penv->user_cal_rcvd)) {
                pr_err(DEVICE " invalid size to write %zu\n", count +
                                penv->user_cal_rcvd);
-               rc = -ENOMEM;
-               goto exit;
+               mutex_unlock(&penv->dev_lock);
+               return -ENOMEM;
        }
-       rc = copy_from_user((void *)penv->user_cal_data +
-                       penv->user_cal_rcvd, user_buffer, count);
-       if (0 == rc) {
+
+       cal_data = kmalloc(count, GFP_KERNEL);
+       if (!cal_data) {
+               mutex_unlock(&penv->dev_lock);
+               return -ENOMEM;
+       }
+
+       rc = copy_from_user(cal_data, user_buffer, count);
+       if (!rc) {
+               memcpy(penv->user_cal_data + penv->user_cal_rcvd,
+                      cal_data, count);
                penv->user_cal_rcvd += count;
                rc += count;
        }
+
+       kfree(cal_data);
        if (penv->user_cal_rcvd == penv->user_cal_exp_size) {
                penv->user_cal_available = true;
                pr_info_ratelimited("wcnss: user cal written");
        }
+       mutex_unlock(&penv->dev_lock);
 
-exit:
        return rc;
 }
 
+static int wcnss_node_release(struct inode *inode, struct file *file)
+{
+       return 0;
+}
 
 static int wcnss_notif_cb(struct notifier_block *this, unsigned long code,
                                void *ss_handle)
@@ -3425,6 +3427,7 @@ static const struct file_operations wcnss_node_fops = {
        .open = wcnss_node_open,
        .read = wcnss_wlan_read,
        .write = wcnss_wlan_write,
+       .release = wcnss_node_release,
 };
 
 static struct miscdevice wcnss_misc = {
@@ -3452,6 +3455,13 @@ wcnss_wlan_probe(struct platform_device *pdev)
        }
        penv->pdev = pdev;
 
+       penv->user_cal_data =
+               devm_kzalloc(&pdev->dev, MAX_CALIBRATED_DATA_SIZE, GFP_KERNEL);
+       if (!penv->user_cal_data) {
+               dev_err(&pdev->dev, "Failed to alloc memory for cal data.\n");
+               return -ENOMEM;
+       }
+
        /* register sysfs entries */
        ret = wcnss_create_sysfs(&pdev->dev);
        if (ret) {
@@ -3472,6 +3482,11 @@ wcnss_wlan_probe(struct platform_device *pdev)
        mutex_init(&penv->pm_qos_mutex);
        init_waitqueue_head(&penv->read_wait);
 
+       penv->user_cal_rcvd = 0;
+       penv->user_cal_read = 0;
+       penv->user_cal_exp_size = 0;
+       penv->user_cal_available = false;
+
        /* Since we were built into the kernel we'll be called as part
         * of kernel initialization.  We don't know if userspace
         * applications are available to service PIL at this time