⬢ DragonFlyBSD Kernel Audit
DF-4494 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/drm/radeon/rs780_dpm.c
+++ b/sys/dev/drm/radeon/rs780_dpm.c
@@ -600,6 +600,11 @@
 	struct radeon_ps *boot_ps = rdev->pm.dpm.boot_ps;
 	int ret;
 
+	if (boot_ps == NULL) {
+		DRM_ERROR("no boot power state, refusing to enable DPM\n");
+		return -EINVAL;
+	}
+
 	rs780_get_pm_mode_parameters(rdev);
 	rs780_disable_vbios_powersaving(rdev);
 
@@ -786,6 +791,15 @@
 	}
 }
 
+/* DF-4492: every pplib walk below is driven by VBIOS-supplied offsets,
+ * indices and strides; none of them were bounds-checked, so a crafted
+ * PowerPlayInfo table reads past the kmalloc'd BIOS image.  Bound each
+ * walk by the table size reported by the ATOM data-table header. */
+static bool rs780_pplib_walk_ok(u16 data_size, u32 rel_end)
+{
+	return rel_end <= (u32)data_size;
+}
+
 static int rs780_parse_power_table(struct radeon_device *rdev)
 {
 	struct radeon_mode_info *mode_info = &rdev->mode_info;
@@ -796,10 +810,11 @@
 	union power_info *power_info;
 	int index = GetIndexIntoMasterTable(DATA, PowerPlayInfo);
 	u16 data_offset;
+	u16 data_size;
 	u8 frev, crev;
 	struct igp_ps *ps;
 
-	if (!atom_parse_data_header(mode_info->atom_context, index, NULL,
+	if (!atom_parse_data_header(mode_info->atom_context, index, &data_size,
 				   &frev, &crev, &data_offset))
 		return -EINVAL;
 	power_info = (union power_info *)((uint8_t*)mode_info->atom_context->bios + data_offset);
@@ -808,18 +823,50 @@
 				  power_info->pplib.ucNumStates, GFP_KERNEL);
 	if (!rdev->pm.dpm.ps)
 		return -ENOMEM;
+	if (power_info->pplib.ucStateEntrySize < 2) {
+		/* ATOM_PPLIB_STATE is 2 bytes; a smaller stride parses no
+		 * state at all while num_ps is still set (DF-4493) */
+		DRM_ERROR("invalid state entry size %d\n",
+			  power_info->pplib.ucStateEntrySize);
+		goto err_free_ps;
+	}
+	if (!rs780_pplib_walk_ok(data_size,
+			(u32)le16_to_cpu(power_info->pplib.usStateArrayOffset) +
+			(u32)power_info->pplib.ucNumStates *
+			power_info->pplib.ucStateEntrySize)) {
+		DRM_ERROR("state array walks past the power table\n");
+		goto err_free_ps;
+	}
 
 	for (i = 0; i < power_info->pplib.ucNumStates; i++) {
 		power_state = (union pplib_power_state *)
 			((uint8_t*)mode_info->atom_context->bios + data_offset +
 			 le16_to_cpu(power_info->pplib.usStateArrayOffset) +
 			 i * power_info->pplib.ucStateEntrySize);
+		if (!rs780_pplib_walk_ok(data_size,
+				(u32)le16_to_cpu(power_info->pplib.usNonClockInfoArrayOffset) +
+				(u32)power_state->v1.ucNonClockStateIndex *
+				power_info->pplib.ucNonClockSize +
+				sizeof(struct _ATOM_PPLIB_NONCLOCK_INFO))) {
+			DRM_ERROR("nonClockInfoIndex %d walks past the table\n",
+				  power_state->v1.ucNonClockStateIndex);
+			goto err_free_ps;
+		}
 		non_clock_info = (struct _ATOM_PPLIB_NONCLOCK_INFO *)
 			((uint8_t*)mode_info->atom_context->bios + data_offset +
 			 le16_to_cpu(power_info->pplib.usNonClockInfoArrayOffset) +
 			 (power_state->v1.ucNonClockStateIndex *
 			  power_info->pplib.ucNonClockSize));
 		if (power_info->pplib.ucStateEntrySize - 1) {
+			if (!rs780_pplib_walk_ok(data_size,
+					(u32)le16_to_cpu(power_info->pplib.usClockInfoArrayOffset) +
+					(u32)power_state->v1.ucClockStateIndices[0] *
+					power_info->pplib.ucClockInfoSize +
+					sizeof(union pplib_clock_info))) {
+				DRM_ERROR("clockInfoIndex %d walks past the table\n",
+					  power_state->v1.ucClockStateIndices[0]);
+				goto err_free_ps;
+			}
 			clock_info = (union pplib_clock_info *)
 				((uint8_t*)mode_info->atom_context->bios + data_offset +
 				 le16_to_cpu(power_info->pplib.usClockInfoArrayOffset) +
@@ -827,7 +874,12 @@
 				  power_info->pplib.ucClockInfoSize));
 			ps = kzalloc(sizeof(struct igp_ps), GFP_KERNEL);
 			if (ps == NULL) {
+				while (i--)
+					kfree(rdev->pm.dpm.ps[i].ps_priv);
 				kfree(rdev->pm.dpm.ps);
+				rdev->pm.dpm.ps = NULL;
+				rdev->pm.dpm.boot_ps = NULL;
+				rdev->pm.dpm.uvd_ps = NULL;
 				return -ENOMEM;
 			}
 			rdev->pm.dpm.ps[i].ps_priv = ps;
@@ -841,6 +893,13 @@
 	}
 	rdev->pm.dpm.num_ps = power_info->pplib.ucNumStates;
 	return 0;
+
+err_free_ps:
+	kfree(rdev->pm.dpm.ps);
+	rdev->pm.dpm.ps = NULL;
+	rdev->pm.dpm.boot_ps = NULL;
+	rdev->pm.dpm.uvd_ps = NULL;
+	return -EINVAL;
 }
 
 int rs780_dpm_init(struct radeon_device *rdev)
@@ -930,7 +989,10 @@
 
 		return 0;
 	}
-	radeon_dpm_fini(rdev);
+	/* DF-4494: do NOT tear the state down here.  radeon_pm_init_dpm
+	 * (radeon_pm.c:1448) discards this error and keeps going, so every
+	 * pointer must stay valid (or NULL) until radeon_pm_fini owns the
+	 * teardown exactly once. */
 	return -EINVAL;
 }