⬢ DragonFlyBSD Kernel Audit
DF-4887 / fix.diff
← back to finding ↓ download raw
--- a/sys/dev/drm/amd/amdgpu/cik_ih.c
+++ b/sys/dev/drm/amd/amdgpu/cik_ih.c
@@ -186,9 +186,18 @@ static void cik_ih_set_interrupt_funcs(str
 static u32 cik_ih_get_wptr(struct amdgpu_device *adev)
 {
 	u32 wptr, tmp;
 
 	wptr = le32_to_cpu(adev->wb.wb[adev->irq.ih.wptr_offs]);
+	/* IV entries are 16 bytes: a wptr that is not 16-byte aligned comes
+	 * from a hostile or faulting device and would permanently spin the
+	 * interrupt walk (the 16-byte-stepping rptr can never equal it).
+	 * Normalize with a warn.
+	 */
+	if ((wptr & ~IH_RB_WPTR__RB_OVERFLOW_MASK) & 0xF)
+		dev_warn(adev->dev, "IH wptr not 16-byte aligned: 0x%08X\n", wptr);
+	wptr &= ~0xF;
 
 	if (wptr & IH_RB_WPTR__RB_OVERFLOW_MASK) {
 		wptr &= ~IH_RB_WPTR__RB_OVERFLOW_MASK;
@@ -198,6 +207,10 @@ static u32 cik_ih_get_wptr(struct amdgpu_d
 		 * from the last not overwritten vector (wptr + 16). Hopefully
 		 * this should allow us to catchup.
 		 */
+		/* derive rptr from the aligned value so a 4-granular wptr
+		 * phase cannot park rptr within 12 bytes of the ring end
+		 * (out-of-bounds decode read)
+		 */
+		wptr &= ~0xF;
 		dev_warn(adev->dev, "IH ring buffer overflow (0x%08X, 0x%08X, 0x%08X)\n",
 			wptr, adev->irq.ih.rptr, (wptr + 16) & adev->irq.ih.ptr_mask);
 		adev->irq.ih.rptr = (wptr + 16) & adev->irq.ih.ptr_mask;
--- a/sys/dev/drm/amd/amdgpu/amdgpu_ih.c
+++ b/sys/dev/drm/amd/amdgpu/amdgpu_ih.c
@@ -130,13 +130,19 @@
  */
 int amdgpu_ih_process(struct amdgpu_device *adev, struct amdgpu_ih_ring *ih,
 		      void (*callback)(struct amdgpu_device *adev,
 				       struct amdgpu_ih_ring *ih))
 {
-	u32 wptr;
+	u32 wptr, count;
 
 	if (!ih->enabled || adev->shutdown)
 		return IRQ_NONE;
 
 	wptr = amdgpu_ih_get_wptr(adev);
+	count = 0;
 
 restart_ih:
 	/* is somebody else already processing irqs? */
 	if (atomic_xchg(&ih->lock, 1))
@@ -151,9 +157,14 @@ int amdgpu_ih_process(struct amdgpu_device
 	/* Order reading of wptr vs. reading of IH ring data */
 	rmb();
 
-	while (ih->rptr != wptr) {
+	/* cap the per-invocation walk so a storm or a bad wptr cannot
+	 * hard-spin the interrupt thread; the next interrupt re-enters.
+	 */
+	while (ih->rptr != wptr && count++ < (ih->ring_size / 16)) {
 		callback(adev, ih);
 		ih->rptr &= ih->ptr_mask;
 	}
+	if (ih->rptr != wptr)
+		dev_warn(adev->dev, "IH walk capped: rptr=0x%08X wptr=0x%08X\n",
+			 ih->rptr, wptr);
 
 	amdgpu_ih_set_rptr(adev);
 	atomic_set(&ih->lock, 0);