qcacld-2.0: Add sanity check for wmi TLV length

Add sanity check for wmi TLV header length before padding/shrinking
elements in a wmi which has a variable length for its TLV structure.

Currently, the TLV length is not checked so its maximum value could
be 65535 which results in a hugh count for elements. Number of elements
is used to terminate the loop for padding/shrinking. If the number
was too large, there would be memory overflow.

CRs-Fixed: 2169157
Change-Id: I99c700d62f8c0db84cbd95fc6efcb5249b89eb1d
diff --git a/CORE/SERVICES/WMI/wmi_tlv_helper.c b/CORE/SERVICES/WMI/wmi_tlv_helper.c
index 7b604c0..960c38d 100644
--- a/CORE/SERVICES/WMI/wmi_tlv_helper.c
+++ b/CORE/SERVICES/WMI/wmi_tlv_helper.c
@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2013-2014,2017 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2013-2014,2017-2018 The Linux Foundation. All rights reserved.
  *
  * Previously licensed under the ISC license by Qualcomm Atheros, Inc.
  *
@@ -375,6 +375,7 @@
     wmitlv_cmd_param_info *cmd_param_tlvs_ptr = NULL;
     A_UINT32  remaining_expected_tlvs=0xFFFFFFFF;
     A_UINT32 len_wmi_cmd_struct_buf;
+    A_UINT32 free_buf_len;
 
     /* Get the number of TLVs for this command/event */
     if (wmitlv_get_attributes(is_cmd_id, wmi_cmd_event_id, WMITLV_GET_ATTRIB_NUM_TLVS, &attr_struct_ptr) != 0)
@@ -426,6 +427,12 @@
         A_UINT32 curr_tlv_len = WMITLV_GET_TLVLEN(WMITLV_GET_HDR(buf_ptr));
         int      num_padding_bytes = 0;
 
+        free_buf_len = param_buf_len - (buf_idx + WMI_TLV_HDR_SIZE);
+        if (curr_tlv_len > free_buf_len) {
+            wmi_tlv_print_error("%s: TLV length overflow", __func__);
+            goto Error_wmitlv_check_and_pad_tlvs;
+        }
+
         /* Get the attributes of the TLV with the given order in "tlv_index" */
         wmi_tlv_OS_MEMZERO(&attr_struct_ptr,sizeof(wmitlv_attributes_struc));
         if (wmitlv_get_attributes(is_cmd_id, wmi_cmd_event_id, tlv_index, &attr_struct_ptr) != 0)
@@ -479,17 +486,19 @@
             {
                 A_UINT32 in_tlv_len = 0;
 
-                if (curr_tlv_len != 0)
-                {
+                if (curr_tlv_len != 0) {
                     in_tlv_len = WMITLV_GET_TLVLEN(WMITLV_GET_HDR(buf_ptr));
                     in_tlv_len += WMI_TLV_HDR_SIZE;
+                    if (in_tlv_len > curr_tlv_len) {
+                        wmi_tlv_print_error("%s: Invalid in_tlv_len=%d",
+                                            __func__, in_tlv_len);
+                        goto Error_wmitlv_check_and_pad_tlvs;
+                    }
                     tlv_size_diff = in_tlv_len - attr_struct_ptr.tag_struct_size;
                     num_of_elems = curr_tlv_len/in_tlv_len;
                     wmi_tlv_print_verbose("%s: WARN: TLV array of structures in_tlv_len=%d struct_size:%d diff:%d num_of_elems=%d \n",
                            __func__, in_tlv_len, attr_struct_ptr.tag_struct_size, tlv_size_diff, num_of_elems);
-                }
-                else
-                {
+                } else {
                     tlv_size_diff = 0;
                     num_of_elems   = 0;
                 }
@@ -568,40 +577,54 @@
 
                 if (tlv_size_diff < 0)
                 {
-                    /* Incoming structure size is smaller than expected size then this needs padding for each element in the array */
+                    /*
+                     * Incoming structure size is smaller than expected size
+                     * then this needs padding for each element in the array
+                     */
 
                     /* Find amount of bytes to be padded for one element */
                     num_padding_bytes = tlv_size_diff * -1;
 
-                    /* Move subsequent TLVs by number of bytes to be padded for all elements */
-                    if (param_buf_len > (buf_idx + curr_tlv_len))
-                    {
+                    /*
+                     * Move subsequent TLVs by number of bytes to be
+                     * padded for all elements
+                     */
+                    if (free_buf_len < attr_struct_ptr.tag_struct_size *
+                                        num_of_elems ||
+                        param_buf_len <  buf_idx + curr_tlv_len +
+                                        num_padding_bytes * num_of_elems) {
+                        wmi_tlv_print_error("%s: Insufficent buffer\n",
+                                            __func__);
+                        goto Error_wmitlv_check_and_pad_tlvs;
+                    } else {
                         src_addr = buf_ptr + curr_tlv_len;
-                        dst_addr = buf_ptr + curr_tlv_len + (num_padding_bytes * num_of_elems);
+                        dst_addr = buf_ptr + curr_tlv_len +
+                                   num_padding_bytes * num_of_elems;
                         buf_mov_len  = param_buf_len - (buf_idx + curr_tlv_len);
 
                         wmi_tlv_OS_MEMMOVE(dst_addr, src_addr, buf_mov_len);
                     }
 
-                    /* Move subsequent elements of array down by number of bytes to be padded for one element and alse set padding bytes to zero */
+                    /*
+                     * Move subsequent elements of array down by number of bytes
+                     * to be padded for one element and alse set padding bytes
+                     * to zero
+                     */
                     tlv_buf_ptr = buf_ptr;
-                    for(i=0; i<num_of_elems; i++)
+                    for (i = 0; i < num_of_elems - 1; i++)
                     {
                         src_addr = tlv_buf_ptr + in_tlv_len;
-                        if (i != (num_of_elems-1))
-                        {
-                            /* Need not move anything for last element in the array */
-                            dst_addr = tlv_buf_ptr + in_tlv_len + num_padding_bytes;
-                            buf_mov_len  = curr_tlv_len - ((i+1) * in_tlv_len);
-
-                            wmi_tlv_OS_MEMMOVE(dst_addr, src_addr, buf_mov_len);
-                        }
+                        dst_addr = tlv_buf_ptr + in_tlv_len + num_padding_bytes;
+                        buf_mov_len  = curr_tlv_len - ((i + 1) * in_tlv_len);
+                        wmi_tlv_OS_MEMMOVE(dst_addr, src_addr, buf_mov_len);
 
                         /* Set the padding bytes to zeroes */
                         wmi_tlv_OS_MEMZERO(src_addr, num_padding_bytes);
 
                         tlv_buf_ptr += attr_struct_ptr.tag_struct_size;
                     }
+                    src_addr = tlv_buf_ptr + in_tlv_len;
+                    wmi_tlv_OS_MEMZERO(src_addr, num_padding_bytes);
 
                     /* Update the number of padding bytes to total number of bytes padded for all elements in the array */
                     num_padding_bytes = num_padding_bytes * num_of_elems;