Browse Source

qcacmn: Fix possible overread in wifi_pos_parse_req

In wifi_pos_parse_req(), payload sections of nl attributes ATTR_DATA and
ATTR_META_DATA are type casted to driver internal structures tAniMsgHdr
and wifi_pos_field_info respectively without validating payload lengths
which can lead to buffer overread if the payload lengths are less than
size of internal structures.

To fix this, avoid type-cast and return error if payload lengths of nl
attributes ATTR_DATA and ATTR_META_DATA are less than size of tAniMsgHdr
and wifi_pos_field_info respectively.

Change-Id: Ie9e3197f2cd3852b394e834991aa8d3a5b530d85
CRs-Fixed: 2471275
Rajeev Kumar Sirasanagandla 5 years ago
parent
commit
d36ba2d9cf

+ 47 - 8
os_if/linux/wifi_pos/src/os_if_wifi_pos.c

@@ -85,6 +85,8 @@ static int  wifi_pos_parse_req(const void *data, int len, int pid,
 {
 	tAniMsgHdr *msg_hdr;
 	struct nlattr *tb[CLD80211_ATTR_MAX + 1];
+	uint32_t msg_len, id, nl_field_info_size, expected_field_info_size;
+	struct wifi_pos_field_info *field_info;
 
 	if (wlan_cfg80211_nla_parse(tb, CLD80211_ATTR_MAX, data, len, NULL)) {
 		osif_err("invalid data in request");
@@ -96,23 +98,54 @@ static int  wifi_pos_parse_req(const void *data, int len, int pid,
 		return OEM_ERR_INVALID_MESSAGE_TYPE;
 	}
 
-	msg_hdr = (tAniMsgHdr *)nla_data(tb[CLD80211_ATTR_DATA]);
-	if (!msg_hdr) {
-		osif_err("msg_hdr null");
-		return OEM_ERR_NULL_MESSAGE_HEADER;
+	msg_len = nla_len(tb[CLD80211_ATTR_DATA]);
+	if (msg_len < sizeof(*msg_hdr)) {
+		osif_err("Insufficient length for msg_hdr: %u", msg_len);
+		return OEM_ERR_INVALID_MESSAGE_LENGTH;
 	}
 
+	msg_hdr = nla_data(tb[CLD80211_ATTR_DATA]);
 	req->msg_type = msg_hdr->type;
+
+	if (msg_len < sizeof(*msg_hdr) + msg_hdr->length) {
+		osif_err("Insufficient length for msg_hdr buffer: %u",
+			 msg_len);
+		return OEM_ERR_INVALID_MESSAGE_LENGTH;
+	}
+
 	req->buf_len = msg_hdr->length;
 	req->buf = (uint8_t *)&msg_hdr[1];
 	req->pid = pid;
 
-	if (tb[CLD80211_ATTR_META_DATA]) {
-		req->field_info_buf = (struct wifi_pos_field_info *)
-					nla_data(tb[CLD80211_ATTR_META_DATA]);
-		req->field_info_buf_len = nla_len(tb[CLD80211_ATTR_META_DATA]);
+	id = CLD80211_ATTR_META_DATA;
+	if (!tb[id])
+		return 0;
+
+	nl_field_info_size = nla_len(tb[id]);
+	if (nl_field_info_size < sizeof(*field_info)) {
+		osif_err("Insufficient length for field_info_buf: %u",
+			 nl_field_info_size);
+		return OEM_ERR_INVALID_MESSAGE_LENGTH;
 	}
 
+	field_info = nla_data(tb[id]);
+	if (!field_info->count) {
+		osif_debug("field_info->count is zero, ignoring META_DATA");
+		return 0;
+	}
+
+	expected_field_info_size = sizeof(*field_info) +
+		(field_info->count - 1) * sizeof(struct wifi_pos_field);
+
+	if (nl_field_info_size < expected_field_info_size) {
+		osif_err("Insufficient len for total no.of %u fields",
+			 field_info->count);
+		return OEM_ERR_INVALID_MESSAGE_LENGTH;
+	}
+
+	req->field_info_buf = field_info;
+	req->field_info_buf_len = nl_field_info_size;
+
 	return 0;
 }
 #else
@@ -129,6 +162,12 @@ static int wifi_pos_parse_req(struct sk_buff *skb, struct wifi_pos_req_msg *req)
 		return OEM_ERR_NULL_MESSAGE_HEADER;
 	}
 
+	if (nlh->nlmsg_len < NLMSG_LENGTH(sizeof(*msg_hdr))) {
+		osif_err("nlmsg_len(%d) and msg_hdr_size(%zu) mis-match",
+			 nlh->nlmsg_len, sizeof(*msg_hdr));
+		return OEM_ERR_INVALID_MESSAGE_LENGTH;
+	}
+
 	msg_hdr = NLMSG_DATA(nlh);
 	if (!msg_hdr) {
 		osif_err("Message header null");

+ 22 - 0
umac/wifi_pos/inc/wifi_pos_api.h

@@ -32,6 +32,28 @@
 struct wlan_objmgr_psoc;
 struct wifi_pos_driver_caps;
 
+/**
+ * struct wifi_pos_field - wifi positioning field element
+ * @id: RTT field id
+ * @offset: data offset in field info buffer
+ * @length: length of related data in field info buffer
+ */
+struct wifi_pos_field {
+	uint32_t id;
+	uint32_t offset;
+	uint32_t length;
+};
+
+/**
+ * struct wifi_pos_field_info - wifi positioning field info buffer
+ * @count: number of @wifi_pos_field elements
+ * @fields: buffer to hold @wifi_pos_field elements
+ */
+struct wifi_pos_field_info {
+	uint32_t count;
+	struct wifi_pos_field fields[1];
+};
+
 #ifdef WIFI_POS_CONVERGED
 /**
  * enum oem_err_msg - err msg returned to user space

+ 1 - 12
umac/wifi_pos/src/wifi_pos_oem_interface_i.h

@@ -1,5 +1,5 @@
 /*
- * Copyright (c) 2017 The Linux Foundation. All rights reserved.
+ * Copyright (c) 2017, 2019 The Linux Foundation. All rights reserved.
  *
  * Permission to use, copy, modify, and/or distribute this software for
  * any purpose with or without fee is hereby granted, provided that the
@@ -43,17 +43,6 @@
 #define WIFI_POS_FLAG_DFS               10
 #define WIFI_POS_SET_DFS(info)          (info |=  (1 << WIFI_POS_FLAG_DFS))
 
-struct wifi_pos_field {
-	uint32_t id;
-	uint32_t offset;
-	uint32_t length;
-};
-
-struct wifi_pos_field_info {
-	uint32_t count;
-	struct wifi_pos_field fields[1];
-};
-
 /**
  * enum WMIRTT_FIELD_ID - identifies which field is being specified
  * @WMIRTT_FIELD_ID_oem_data_sub_type: oem data req sub type