Browse Source

qcacld-3.0: Parse OCI IE properly to avoid OOB

There is no valid check for oci ie_len buffer while
typecasting, which may lead to OOB access.

Use dot11f_unpack_ie_oci to parse OCI IE to avoid
OOB read.

Change-Id: Iad8cc82072e8d729a4b95bc04c2e8df31e2582f4
CRs-Fixed: 3581129
Aravind Kishore Sukla 1 year ago
parent
commit
e0181c4873
1 changed files with 30 additions and 13 deletions
  1. 30 13
      core/mac/src/pe/lim/lim_process_action_frame.c

+ 30 - 13
core/mac/src/pe/lim/lim_process_action_frame.c

@@ -64,6 +64,11 @@
 (DOT11F_FF_CATEGORY_LEN + DOT11F_FF_ACTION_LEN + DOT11F_FF_TRANSACTIONID_LEN)
 #define SA_QUERY_IE_OFFSET (4)
 
+#define MIN_OCI_IE_LEN 6
+#define OCI_IE_OUI_SIZE 1
+#define OCI_IE_OP_CLS_OFFSET 3
+#define ELE_ID_EXT_LEN 1
+
 static last_processed_msg rrm_link_action_frm;
 
 /**-----------------------------------------------------------------
@@ -1222,16 +1227,22 @@ static bool
 lim_check_oci_match(struct mac_context *mac, struct pe_session *pe_session,
 		    uint8_t *ie, uint8_t *peer, uint32_t ie_len)
 {
-	const uint8_t *oci_ie;
-	tDot11fIEoci self_oci, *peer_oci;
+	const uint8_t *oci_ie, ext_id_param = WLAN_EXTN_ELEMID_OCI;
+	tDot11fIEoci self_oci, peer_oci = {0};
 	uint16_t peer_chan_width;
 	uint16_t local_peer_chan_width = 0;
 	uint8_t country_code[CDS_COUNTRY_CODE_LEN + 1];
+	uint32_t status = DOT11F_PARSE_SUCCESS;
 
 	if (!lim_is_self_and_peer_ocv_capable(mac, peer, pe_session))
 		return true;
 
-	oci_ie = wlan_get_ie_ptr_from_eid(DOT11F_EID_OCI, ie, ie_len);
+	if (ie_len < MIN_OCI_IE_LEN)
+		return false;
+
+	oci_ie = wlan_get_ext_ie_ptr_from_ext_id(&ext_id_param,
+						 OCI_IE_OUI_SIZE,
+						 ie, ie_len);
 	if (!oci_ie) {
 		pe_err("OCV not found OCI in SA Query frame!");
 		return false;
@@ -1245,29 +1256,35 @@ lim_check_oci_match(struct mac_context *mac, struct pe_session *pe_session,
 	 * Primary channel      : 1 byte
 	 * Freq_seg_1_ch_num    : 1 byte
 	 */
-	peer_oci = (tDot11fIEoci *)&oci_ie[2];
+	status = dot11f_unpack_ie_oci(mac,
+				      (uint8_t *)&oci_ie[OCI_IE_OP_CLS_OFFSET],
+				      oci_ie[SIR_MAC_IE_LEN_OFFSET] -
+				      ELE_ID_EXT_LEN,
+				      &peer_oci, false);
+	if (!DOT11F_SUCCEEDED(status) || !peer_oci.present)
+		return false;
 
 	wlan_reg_read_current_country(mac->psoc, country_code);
 	peer_chan_width =
 	wlan_reg_dmn_get_chanwidth_from_opclass_auto(
 			country_code,
-			peer_oci->prim_ch_num,
-			peer_oci->op_class);
+			peer_oci.prim_ch_num,
+			peer_oci.op_class);
 
 	lim_fill_oci_params(mac, pe_session, &self_oci, peer,
 			    &local_peer_chan_width);
-	if (((self_oci.op_class != peer_oci->op_class) &&
-	     (local_peer_chan_width > peer_chan_width)) ||
-	    (self_oci.prim_ch_num != peer_oci->prim_ch_num) ||
-	    (self_oci.freq_seg_1_ch_num != peer_oci->freq_seg_1_ch_num)) {
+	if ((self_oci.op_class != peer_oci.op_class &&
+	     local_peer_chan_width > peer_chan_width) ||
+	    self_oci.prim_ch_num != peer_oci.prim_ch_num ||
+	    self_oci.freq_seg_1_ch_num != peer_oci.freq_seg_1_ch_num) {
 		pe_err("OCI mismatch,self %d %d %d %d, peer %d %d %d %d",
 		       self_oci.op_class,
 		       self_oci.prim_ch_num,
 		       self_oci.freq_seg_1_ch_num,
 		       local_peer_chan_width,
-		       peer_oci->op_class,
-		       peer_oci->prim_ch_num,
-		       peer_oci->freq_seg_1_ch_num,
+		       peer_oci.op_class,
+		       peer_oci.prim_ch_num,
+		       peer_oci.freq_seg_1_ch_num,
 		       peer_chan_width);
 		return false;
 	}