qcacmn: Add individual length checks to Beacon Information element

Currently there is no individual length check to each IE, which
could probably result in buffer overead. Minimum length should
be checked for each varibale IE for avoid the same. Also some fixed
IEs should have a length check of not greater than the size of
their respective structures to avoid corrupting other IE data.

Fix is to add a length check to each individual IE to avoid
corrupting other IEs and also to prevent reception of any IE
of invalid length IE.

Change-Id: I9a0914861d7ff2871ac72ad7357ebbb7ef10eeb3
CRs-Fixed: 2183014
diff --git a/umac/scan/dispatcher/src/wlan_scan_utils_api.c b/umac/scan/dispatcher/src/wlan_scan_utils_api.c
index 4eb39ea..fa8ecfa 100644
--- a/umac/scan/dispatcher/src/wlan_scan_utils_api.c
+++ b/umac/scan/dispatcher/src/wlan_scan_utils_api.c
@@ -341,7 +341,7 @@
 	return true;
 }
 
-static void
+static QDF_STATUS
 util_scan_parse_extn_ie(struct scan_cache_entry *scan_params,
 	struct ie_header *ie)
 {
@@ -352,24 +352,32 @@
 		scan_params->ie_list.srp   = (uint8_t *)ie;
 		break;
 	case WLAN_EXTN_ELEMID_HECAP:
+		if ((extn_ie->ie_len < WLAN_HE_CAP_IE_MIN_LEN) ||
+		    (extn_ie->ie_len > WLAN_HE_CAP_IE_MAX_LEN))
+			return QDF_STATUS_E_INVAL;
 		scan_params->ie_list.hecap = (uint8_t *)ie;
 		break;
 	case WLAN_EXTN_ELEMID_HEOP:
+		if ((extn_ie->ie_len < WLAN_HE_OP_IE_MIN_LEN) ||
+		    (extn_ie->ie_len > WLAN_HE_OP_IE_MAX_LEN))
+			return QDF_STATUS_E_INVAL;
 		scan_params->ie_list.heop  = (uint8_t *)ie;
 		break;
 	case WLAN_EXTN_ELEMID_ESP:
+		if (extn_ie->ie_len > WLAN_EXT_ESP_IE_MAX_LEN)
+			return QDF_STATUS_E_INVAL;
 		scan_params->ie_list.esp = (uint8_t *)ie;
 		break;
 	default:
 		break;
 	}
+	return QDF_STATUS_SUCCESS;
 }
 
-static void
+static QDF_STATUS
 util_scan_parse_vendor_ie(struct scan_cache_entry *scan_params,
 	struct ie_header *ie)
 {
-
 	if (scan_params->ie_list.vendor == NULL)
 		scan_params->ie_list.vendor = (uint8_t *)ie;
 
@@ -397,20 +405,51 @@
 		scan_params->ie_list.sonadv = (uint8_t *)ie;
 	} else if (is_ht_cap((uint8_t *)ie)) {
 		/* we only care if there isn't already an HT IE (ANA) */
-		if (scan_params->ie_list.htcap == NULL)
+		if (scan_params->ie_list.htcap == NULL) {
+			if (ie->ie_len != (WLAN_VENDOR_HT_IE_OFFSET_LEN +
+					   sizeof(struct htcap_cmn_ie)))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.htcap =
-			  (uint8_t *)&(((struct wlan_vendor_ie_htcap *)ie)->ie);
+			 (uint8_t *)&(((struct wlan_vendor_ie_htcap *)ie)->ie);
+		}
 	} else if (is_ht_info((uint8_t *)ie)) {
 		/* we only care if there isn't already an HT IE (ANA) */
-		if (scan_params->ie_list.htinfo == NULL)
+		if (scan_params->ie_list.htinfo == NULL) {
+			if (ie->ie_len != WLAN_VENDOR_HT_IE_OFFSET_LEN +
+					  sizeof(struct wlan_ie_htinfo_cmn))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.htinfo =
 			  (uint8_t *)&(((struct wlan_vendor_ie_htinfo *)
 			  ie)->hi_ie);
+		}
 	} else if (is_interop_vht((uint8_t *)ie) &&
 	    !(scan_params->ie_list.vhtop)) {
+		uint8_t *vendor_ie = (uint8_t *)(ie);
+
+		if (ie->ie_len < ((WLAN_VENDOR_VHTCAP_IE_OFFSET +
+				 sizeof(struct wlan_ie_vhtcaps)) -
+				 sizeof(struct ie_header)))
+			return QDF_STATUS_E_INVAL;
+		vendor_ie = ((uint8_t *)(ie)) + WLAN_VENDOR_VHTCAP_IE_OFFSET;
+		if (vendor_ie[1] != (sizeof(struct wlan_ie_vhtcaps)) -
+				      sizeof(struct ie_header))
+			return QDF_STATUS_E_INVAL;
 		/* location where Interop Vht Cap IE and VHT OP IE Present */
-		scan_params->ie_list.vhtcap = (((uint8_t *)(ie)) + 7);
-		scan_params->ie_list.vhtop = (((uint8_t *)(ie)) + 21);
+		scan_params->ie_list.vhtcap = (((uint8_t *)(ie)) +
+						WLAN_VENDOR_VHTCAP_IE_OFFSET);
+		if (ie->ie_len > ((WLAN_VENDOR_VHTCAP_IE_OFFSET +
+				 sizeof(struct wlan_ie_vhtcaps)) -
+				 sizeof(struct ie_header)) &&
+		    ie->ie_len < ((WLAN_VENDOR_VHTOP_IE_OFFSET +
+				  sizeof(struct wlan_ie_vhtop)) -
+				  sizeof(struct ie_header)))
+			return QDF_STATUS_E_INVAL;
+		vendor_ie = ((uint8_t *)(ie)) + WLAN_VENDOR_VHTOP_IE_OFFSET;
+		if (vendor_ie[1] != (sizeof(struct wlan_ie_vhtop) -
+				     sizeof(struct ie_header)))
+			return QDF_STATUS_E_INVAL;
+		scan_params->ie_list.vhtop = (((uint8_t *)(ie)) +
+						WLAN_VENDOR_VHTOP_IE_OFFSET);
 	} else if (is_bwnss_oui((uint8_t *)ie)) {
 		/*
 		 * Bandwidth-NSS map has sub-type & version.
@@ -420,6 +459,7 @@
 	} else if (is_mbo_oce_oui((uint8_t *)ie)) {
 		scan_params->ie_list.mbo_oce = (uint8_t *)ie;
 	}
+	return QDF_STATUS_SUCCESS;
 }
 
 static QDF_STATUS
@@ -449,56 +489,89 @@
 
 		switch (ie->ie_id) {
 		case WLAN_ELEMID_SSID:
+			if (ie->ie_len > (sizeof(struct ie_ssid) -
+					  sizeof(struct ie_header)))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.ssid = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_RATES:
+			if (ie->ie_len > WLAN_SUPPORTED_RATES_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.rates = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_DSPARMS:
+			if (ie->ie_len != WLAN_DS_PARAM_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.ds_param = (uint8_t *)ie;
 			scan_params->channel.chan_idx =
 				((struct ds_ie *)ie)->cur_chan;
 			break;
 		case WLAN_ELEMID_TIM:
+			if (ie->ie_len < WLAN_TIM_IE_MIN_LENGTH)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.tim = (uint8_t *)ie;
 			scan_params->dtim_period =
 				((struct wlan_tim_ie *)ie)->tim_period;
 			break;
 		case WLAN_ELEMID_COUNTRY:
+			if (ie->ie_len < WLAN_COUNTRY_IE_MIN_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.country = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_QBSS_LOAD:
+			if (ie->ie_len != sizeof(struct qbss_load_ie) -
+					  sizeof(struct ie_header))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.qbssload = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_CHANSWITCHANN:
+			if (ie->ie_len != WLAN_CSA_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.csa = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_IBSSDFS:
+			if (ie->ie_len < WLAN_IBSSDFS_IE_MIN_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.ibssdfs = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_QUIET:
+			if (ie->ie_len != WLAN_QUIET_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.quiet = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_ERP:
+			if (ie->ie_len != (sizeof(struct erp_ie) -
+					    sizeof(struct ie_header)))
+				return QDF_STATUS_E_INVAL;
 			scan_params->erp = ((struct erp_ie *)ie)->value;
 			break;
 		case WLAN_ELEMID_HTCAP_ANA:
+			if (ie->ie_len != sizeof(struct htcap_cmn_ie))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.htcap =
 				(uint8_t *)&(((struct htcap_ie *)ie)->ie);
 			break;
 		case WLAN_ELEMID_RSN:
+			if (ie->ie_len < WLAN_RSN_IE_MIN_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.rsn = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_XRATES:
 			scan_params->ie_list.xrates = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_EXTCHANSWITCHANN:
+			if (ie->ie_len != WLAN_XCSA_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.xcsa = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_SECCHANOFFSET:
+			if (ie->ie_len != WLAN_SECCHANOFF_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.secchanoff = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_HTINFO_ANA:
+			if (ie->ie_len != sizeof(struct wlan_ie_htinfo_cmn))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.htinfo =
 			  (uint8_t *)&(((struct wlan_ie_htinfo *) ie)->hi_ie);
 			scan_params->channel.chan_idx =
@@ -506,26 +579,42 @@
 			  (scan_params->ie_list.htinfo))->hi_ctrlchannel;
 			break;
 		case WLAN_ELEMID_WAPI:
+			if (ie->ie_len < WLAN_WAPI_IE_MIN_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.wapi = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_XCAPS:
+			if (ie->ie_len > WLAN_EXTCAP_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.extcaps = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_VHTCAP:
+			if (ie->ie_len != (sizeof(struct wlan_ie_vhtcaps) -
+					   sizeof(struct ie_header)))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.vhtcap = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_VHTOP:
+			if (ie->ie_len != (sizeof(struct wlan_ie_vhtop) -
+					   sizeof(struct ie_header)))
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.vhtop = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_OP_MODE_NOTIFY:
+			if (ie->ie_len != WLAN_OPMODE_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 				scan_params->ie_list.opmode = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_MOBILITY_DOMAIN:
+			if (ie->ie_len != WLAN_MOBILITY_DOMAIN_IE_MAX_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.mdie = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_VENDOR:
-			util_scan_parse_vendor_ie(scan_params,
-				ie);
+			status = util_scan_parse_vendor_ie(scan_params,
+							   ie);
+			if (QDF_IS_STATUS_ERROR(status))
+				return status;
 			break;
 		case WLAN_ELEMID_CHAN_SWITCH_WRAP:
 			scan_params->ie_list.cswrp = (uint8_t *)ie;
@@ -542,10 +631,14 @@
 			}
 			break;
 		case WLAN_ELEMID_FILS_INDICATION:
+			if (ie->ie_len < WLAN_FILS_INDICATION_IE_MIN_LEN)
+				return QDF_STATUS_E_INVAL;
 			scan_params->ie_list.fils_indication = (uint8_t *)ie;
 			break;
 		case WLAN_ELEMID_EXTN_ELEM:
-			util_scan_parse_extn_ie(scan_params, ie);
+			status = util_scan_parse_extn_ie(scan_params, ie);
+			if (QDF_IS_STATUS_ERROR(status))
+				return status;
 			break;
 		default:
 			break;