瀏覽代碼

msm: camera: cdm: Prevention from OOB on virtual cdm write

Adds proper sanity on virtual cdm right operation to avoid
OOB register space access. After getting the mapped buffers
through cam_mem_get_cpu_buf() in kernel, userspace is still
able to access those buffers. So, the validation of offsets
and sizes does not have any advantage if userspace can alter
those parameters after we have validated them. That's why we
copied the information in kernel before we validate that.

CRs-Fixed: 3778045
Change-Id: I3b53549419b40a92333263e29d93220577d691e9
Signed-off-by: Yash Upadhyay <[email protected]>
Yash Upadhyay 1 年之前
父節點
當前提交
d6d05fba0b
共有 1 個文件被更改,包括 201 次插入62 次删除
  1. 201 62
      drivers/cam_cdm/cam_cdm_util.c

+ 201 - 62
drivers/cam_cdm/cam_cdm_util.c

@@ -1,7 +1,7 @@
 // SPDX-License-Identifier: GPL-2.0-only
 /*
  * Copyright (c) 2017-2021, The Linux Foundation. All rights reserved.
- * Copyright (c) 2022-2023, Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2022-2024, Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #include <linux/types.h>
@@ -489,115 +489,253 @@ int cam_cdm_get_ioremap_from_base(uint32_t hw_base,
 	return ret;
 }
 
-static int cam_cdm_util_reg_cont_write(void __iomem *base_addr,
-	uint32_t *cmd_buf, uint32_t cmd_buf_size, uint32_t *used_bytes)
+static int cam_cdm_util_cmd_buf_validation(void __iomem *base_addr,
+	uint32_t base_array_size,
+	struct cam_soc_reg_map *base_table[CAM_SOC_MAX_BLOCK],
+	uint32_t cmd_buf_size, uint32_t *cmd_buf, void *buf,
+	resource_size_t *size,
+	enum cam_cdm_command cmd_type)
 {
-	int ret = 0;
-	uint32_t *data;
-	struct cdm_regcontinuous_cmd *reg_cont;
 
-	if ((cmd_buf_size < cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_CONT)) ||
-		(!base_addr)) {
-		CAM_ERR(CAM_CDM, "invalid base addr and data length  %d %pK",
-			cmd_buf_size, base_addr);
+	int i, ret = 0;
+
+	if (!base_addr) {
+		CAM_ERR(CAM_CDM, "invalid base address");
+		return -EINVAL;
+	}
+
+	for (i = 0; i < base_array_size; i++) {
+		if ((base_table[i]) &&
+			((base_table[i])->mem_base == base_addr)) {
+			*size = (base_table[i])->size;
+			break;
+		}
+	}
+
+	if (*size == 0) {
+		CAM_ERR(CAM_CDM, "Could not retrieve ioremap size, address not mapped!");
 		return -EINVAL;
 	}
 
-	reg_cont = (struct cdm_regcontinuous_cmd *)cmd_buf;
-	if ((!reg_cont->count) || (((reg_cont->count * sizeof(uint32_t)) +
+	switch (cmd_type) {
+	case CAM_CDM_CMD_REG_RANDOM: {
+		struct cdm_regrandom_cmd *reg_random = (struct cdm_regrandom_cmd *)buf;
+		uint32_t *data, offset;
+
+		if ((!reg_random->count) || (((reg_random->count * (sizeof(uint32_t) * 2)) +
+			cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_RANDOM)) >
+				cmd_buf_size)) {
+			CAM_ERR(CAM_CDM, "invalid reg_count  %d cmd_buf_size %d",
+				reg_random->count, cmd_buf_size);
+			ret = -EINVAL;
+		}
+
+		data = cmd_buf + cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_RANDOM);
+
+		for (i = 0; i < reg_random->count; i++) {
+			offset = data[0];
+			if (offset > *size) {
+				CAM_ERR(CAM_CDM, "Offset out of mapped range! size:%llu offset:%u",
+					*size, offset);
+				return -EINVAL;
+			}
+			data += 2;
+		}
+		}
+		break;
+	case CAM_CDM_CMD_REG_CONT: {
+		struct cdm_regcontinuous_cmd *reg_cont = (struct cdm_regcontinuous_cmd *) buf;
+
+		if ((!reg_cont->count) || (((reg_cont->count * sizeof(uint32_t)) +
 			cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_CONT)) >
 			cmd_buf_size)) {
-		CAM_ERR(CAM_CDM, "buffer size %d is not sufficient for count%d",
-			cmd_buf_size, reg_cont->count);
-		return -EINVAL;
+			CAM_ERR(CAM_CDM, "buffer size %d is not sufficient for count%d",
+				cmd_buf_size, reg_cont->count);
+			ret = -EINVAL;
+		}
+
+		if ((reg_cont->offset > *size) && ((reg_cont->offset +
+			(reg_cont->count * sizeof(uint32_t))) > *size)) {
+			CAM_ERR(CAM_CDM, "Offset out of mapped range! size: %lu, offset: %u",
+				*size, reg_cont->offset);
+			return -EINVAL;
+		}
+		}
+		break;
+	case CAM_CDM_CMD_SWD_DMI_64: {
+		struct cdm_dmi_cmd *swd_dmi = (struct cdm_dmi_cmd *) buf;
+
+		if (cmd_buf_size < (cam_cdm_required_size_dmi() + swd_dmi->length + 1)) {
+			CAM_ERR(CAM_CDM, "invalid CDM_SWD_DMI length %d",
+				swd_dmi->length + 1);
+			ret = -EINVAL;
+		}
+
+		if ((swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET > *size) ||
+			(swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_HI_OFFSET > *size)) {
+			CAM_ERR(CAM_CDM,
+				"Offset out of mapped range! size:%llu lo_offset:%u hi_offset:%u",
+				*size, swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET,
+				swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET);
+			return -EINVAL;
+		}
+		}
+		break;
+	case CAM_CDM_CMD_SWD_DMI_32: {
+		struct cdm_dmi_cmd *swd_dmi = (struct cdm_dmi_cmd *) buf;
+
+		if (cmd_buf_size < (cam_cdm_required_size_dmi() + swd_dmi->length + 1)) {
+			CAM_ERR(CAM_CDM, "invalid CDM_SWD_DMI length %d",
+				swd_dmi->length + 1);
+			ret = -EINVAL;
+		}
+
+		if (swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET > *size) {
+			CAM_ERR(CAM_CDM,
+				"Offset out of mapped range! size:%llu lo_offset:%u",
+				*size, swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET);
+			return -EINVAL;
+		}
+		}
+		break;
+	case CAM_CDM_CMD_DMI: {
+		struct cdm_dmi_cmd *swd_dmi = (struct cdm_dmi_cmd *) buf;
+
+		if (cmd_buf_size < (cam_cdm_required_size_dmi() + swd_dmi->length + 1)) {
+			CAM_ERR(CAM_CDM, "invalid CDM_SWD_DMI length %d",
+				swd_dmi->length + 1);
+			ret = -EINVAL;
+		}
+
+		if (swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_OFFSET > *size) {
+			CAM_ERR(CAM_CDM,
+				"Offset out of mapped range! size:%llu offset:%u",
+				*size, swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_OFFSET);
+			return -EINVAL;
+		}
+		}
+		break;
+	default:
+		CAM_ERR(CAM_CDM, "unsupported cdm_cmd_type type 0%x",
+		cmd_type);
+		ret = -EINVAL;
+		break;
 	}
+
+	return ret;
+}
+
+static int cam_cdm_util_reg_cont_write(void __iomem *base_addr,
+	uint32_t *cmd_buf, uint32_t cmd_buf_size, uint32_t *used_bytes,
+	uint32_t base_array_size,
+	struct cam_soc_reg_map *base_table[CAM_SOC_MAX_BLOCK])
+{
+	int rc;
+	uint32_t *data;
+	struct cdm_regcontinuous_cmd reg_cont;
+	resource_size_t size = 0;
+
+	memcpy(&reg_cont, cmd_buf, sizeof(struct cdm_regcontinuous_cmd));
+	rc = cam_cdm_util_cmd_buf_validation(base_addr, base_array_size, base_table,
+		cmd_buf_size, cmd_buf, (void *)&reg_cont,
+		&size, CAM_CDM_CMD_REG_CONT);
+	if (rc) {
+		CAM_ERR(CAM_CDM, "Validation failed! rc=%d", rc);
+		return rc;
+	}
+
 	data = cmd_buf + cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_CONT);
-	cam_io_memcpy(base_addr + reg_cont->offset,	data,
-		reg_cont->count * sizeof(uint32_t));
 
-	*used_bytes = (reg_cont->count * sizeof(uint32_t)) +
+	cam_io_memcpy(base_addr + reg_cont.offset, data,
+		reg_cont.count * sizeof(uint32_t));
+	*used_bytes = (reg_cont.count * sizeof(uint32_t)) +
 		(4 * cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_CONT));
 
-	return ret;
+	return rc;
 }
 
 static int cam_cdm_util_reg_random_write(void __iomem *base_addr,
-	uint32_t *cmd_buf, uint32_t cmd_buf_size, uint32_t *used_bytes)
+	uint32_t *cmd_buf, uint32_t cmd_buf_size, uint32_t *used_bytes,
+	uint32_t base_array_size,
+	struct cam_soc_reg_map *base_table[CAM_SOC_MAX_BLOCK])
 {
-	uint32_t i;
-	struct cdm_regrandom_cmd *reg_random;
-	uint32_t *data;
-
-	if (!base_addr) {
-		CAM_ERR(CAM_CDM, "invalid base address");
-		return -EINVAL;
+	int i, rc;
+	struct cdm_regrandom_cmd reg_random;
+	uint32_t *data, offset;
+	resource_size_t size = 0;
+
+	memcpy(&reg_random, cmd_buf, sizeof(struct cdm_regrandom_cmd));
+
+	rc = cam_cdm_util_cmd_buf_validation(base_addr, base_array_size, base_table,
+		cmd_buf_size, cmd_buf, (void *)&reg_random,
+		&size, CAM_CDM_CMD_REG_RANDOM);
+	if (rc) {
+		CAM_ERR(CAM_CDM, "Validation failed! rc=%d", rc);
+		return rc;
 	}
 
-	reg_random = (struct cdm_regrandom_cmd *) cmd_buf;
-	if ((!reg_random->count) || (((reg_random->count * (sizeof(uint32_t) * 2)) +
-		cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_RANDOM)) >
-			cmd_buf_size)) {
-		CAM_ERR(CAM_CDM, "invalid reg_count  %d cmd_buf_size %d",
-			reg_random->count, cmd_buf_size);
-		return -EINVAL;
-	}
 	data = cmd_buf + cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_RANDOM);
 
-	for (i = 0; i < reg_random->count; i++) {
+	for (i = 0; i < reg_random.count; i++) {
+		offset = data[0];
 		CAM_DBG(CAM_CDM, "reg random: offset %pK, value 0x%x",
-			((void __iomem *)(base_addr + data[0])),
+			((void __iomem *)(base_addr + offset)),
 			data[1]);
-		cam_io_w(data[1], base_addr + data[0]);
+		cam_io_w(data[1], base_addr + offset);
 		data += 2;
 	}
 
-	*used_bytes = ((reg_random->count * (sizeof(uint32_t) * 2)) +
+	*used_bytes = ((reg_random.count * (sizeof(uint32_t) * 2)) +
 		(4 * cam_cdm_get_cmd_header_size(CAM_CDM_CMD_REG_RANDOM)));
 
-	return 0;
+	return rc;
 }
 
 static int cam_cdm_util_swd_dmi_write(uint32_t cdm_cmd_type,
 	void __iomem *base_addr, uint32_t *cmd_buf, uint32_t cmd_buf_size,
-	uint32_t *used_bytes)
+	uint32_t *used_bytes, uint32_t base_array_size,
+	struct cam_soc_reg_map *base_table[CAM_SOC_MAX_BLOCK])
 {
-	uint32_t i;
-	struct cdm_dmi_cmd *swd_dmi;
+	int i, rc;
+	struct cdm_dmi_cmd swd_dmi;
 	uint32_t *data;
-
-	swd_dmi = (struct cdm_dmi_cmd *)cmd_buf;
-
-	if (cmd_buf_size < (cam_cdm_required_size_dmi() + swd_dmi->length + 1)) {
-		CAM_ERR(CAM_CDM, "invalid CDM_SWD_DMI length %d",
-			swd_dmi->length + 1);
-		return -EINVAL;
+	resource_size_t size = 0;
+
+	memcpy(&swd_dmi, cmd_buf, sizeof(struct cdm_dmi_cmd));
+	rc = cam_cdm_util_cmd_buf_validation(base_addr, base_array_size, base_table,
+		cmd_buf_size, cmd_buf, (void *)&swd_dmi,
+		&size, cdm_cmd_type);
+	if (rc) {
+		CAM_ERR(CAM_CDM, "Validation failed! rc=%d", rc);
+		return rc;
 	}
+
 	data = cmd_buf + cam_cdm_required_size_dmi();
 
 	if (cdm_cmd_type == CAM_CDM_CMD_SWD_DMI_64) {
-		for (i = 0; i < (swd_dmi->length + 1)/8; i++) {
+		for (i = 0; i < (swd_dmi.length + 1)/8; i++) {
 			cam_io_w_mb(data[0], base_addr +
-				swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET);
+				swd_dmi.DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET);
 			cam_io_w_mb(data[1], base_addr +
-				swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_HI_OFFSET);
+				swd_dmi.DMIAddr + CAM_CDM_DMI_DATA_HI_OFFSET);
 			data += 2;
 		}
 	} else if (cdm_cmd_type == CAM_CDM_CMD_DMI) {
-		for (i = 0; i < (swd_dmi->length + 1)/4; i++) {
+		for (i = 0; i < (swd_dmi.length + 1)/4; i++) {
 			cam_io_w_mb(data[0], base_addr +
-				swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_OFFSET);
+				swd_dmi.DMIAddr + CAM_CDM_DMI_DATA_OFFSET);
 			data += 1;
 		}
 	} else {
-		for (i = 0; i < (swd_dmi->length + 1)/4; i++) {
+		for (i = 0; i < (swd_dmi.length + 1)/4; i++) {
 			cam_io_w_mb(data[0], base_addr +
-				swd_dmi->DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET);
+				swd_dmi.DMIAddr + CAM_CDM_DMI_DATA_LO_OFFSET);
 			data += 1;
 		}
 	}
-	*used_bytes = (4 * cam_cdm_required_size_dmi()) + swd_dmi->length + 1;
+	*used_bytes = (4 * cam_cdm_required_size_dmi()) + swd_dmi.length + 1;
 
-	return 0;
+	return rc;
 }
 
 int cam_cdm_util_cmd_buf_write(void __iomem **current_device_base,
@@ -615,7 +753,8 @@ int cam_cdm_util_cmd_buf_write(void __iomem **current_device_base,
 		switch (cdm_cmd_type) {
 		case CAM_CDM_CMD_REG_CONT: {
 			ret = cam_cdm_util_reg_cont_write(*current_device_base,
-				cmd_buf, cmd_buf_size, &used_bytes);
+				cmd_buf, cmd_buf_size, &used_bytes,
+				base_array_size, base_table);
 			if (ret)
 				break;
 
@@ -628,7 +767,7 @@ int cam_cdm_util_cmd_buf_write(void __iomem **current_device_base,
 		case CAM_CDM_CMD_REG_RANDOM: {
 			ret = cam_cdm_util_reg_random_write(
 				*current_device_base, cmd_buf, cmd_buf_size,
-				&used_bytes);
+				&used_bytes, base_array_size, base_table);
 			if (ret)
 				break;
 
@@ -650,7 +789,7 @@ int cam_cdm_util_cmd_buf_write(void __iomem **current_device_base,
 			}
 			ret = cam_cdm_util_swd_dmi_write(cdm_cmd_type,
 				*current_device_base, cmd_buf, cmd_buf_size,
-				&used_bytes);
+				&used_bytes, base_array_size, base_table);
 			if (ret)
 				break;