β¬’ DragonFlyBSD Kernel Audit
← triage Β· dashboard
DF-4642

dce_virtual_pageflip dereferences crtcs[crtc_id] before the crtc_id bounds check (latent OOB read)

Summary

The array read precedes its own bounds check; today's sole caller always passes a bounded id so the defect is latent hardening (upstream carries the same inverted order) - one refactor away from a live OOB read of a kernel pointer array. Reorder-check-above-read fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4642 Β· 4 files
FileTypeDescriptionSize
README.md β€” 1.2 KB ↓ raw
VERDICT.md β€” 2.2 KB ↓ raw
verdict.json β€” 2.0 KB view raw
manifest.json β€” 474 B view raw

DF-4642 β€” dce_virtual_pageflip reads crtcs[crtc_id] before bounds-checking crtc_id

  • File: sys/dev/drm/amd/amdgpu/dce_virtual.c:662-667
  • Severity: Info (hardening) β€” latent OOB read; no demonstrated impact on this tree
  • Status: untested β€” requires an AMD GPU with the amdgpu kld module loaded and amdgpu.virtual_display=1; the audit QEMU guest has no AMD GPU and amdgpu is not in GENERIC (module-only: sys/dev/drm/amd/amd/Makefile), so the code path cannot be exercised here.

Why it is still latent-only today

dce_virtual_pageflip() is static and has exactly one caller: dce_virtual_vblank_timer_handle() (dce_virtual.c:708) which passes amdgpu_crtc->crtc_id β€” always index from dce_virtual_crtc_init() in [0, adev->mode_info.num_crtc) and num_crtc is clamped to 1..6 (amdgpu_device.c:1271-1275) against crtcs[AMDGPU_MAX_CRTCS=6] (amdgpu_mode.h:65,327). So the pre-check dereference at line 662 never indexes OOB from the current call graph; it becomes a live OOB read the moment any future caller passes an unchecked id (upstream Linux carries the same inverted order).

Fix

Move the crtc_id >= num_crtc test above the array access.

VERDICT.md
↓ download raw

DF-4642 β€” VERDICT

Not verified on the guest (untested). The code-order defect is proven statically:

static int dce_virtual_pageflip(struct amdgpu_device *adev, unsigned crtc_id)
{
    ...
    amdgpu_crtc = adev->mode_info.crtcs[crtc_id];      /* 662: OOB read for bad id */

    if (crtc_id >= adev->mode_info.num_crtc) {         /* 664: check AFTER use */
        DRM_ERROR("invalid pageflip crtc %d\n", crtc_id);
        return -EINVAL;
    }

Chain traced: - Only caller: dce_virtual_vblank_timer_handle (dce_virtual.c:708) passes amdgpu_crtc->crtc_id, initialized to index < num_crtc at dce_virtual.c:246. - num_crtc clamped to [1,6] in amdgpu_device_enable_virtual_display (amdgpu_device.c:1271-1275); array is crtcs[AMDGPU_MAX_CRTCS], AMDGPU_MAX_CRTCS=6 (amdgpu_mode.h:65,327). - Therefore no OOB is reachable from the current call graph β†’ Info/hardening, not a demonstrable memory-safety bug on this tree.

Why untested rather than not_reproduced: the driver cannot even load on the audit guest (no AMD PCI device; amdgpu exists only as a kld module, sys/dev/drm/amd/amd/Makefile β†’ SUBDIR amdgpu; GENERIC sys/config/X86_64_GENERIC does not include it). Running the path would require AMD hardware.

--- a/sys/dev/drm/amd/amdgpu/dce_virtual.c
+++ b/sys/dev/drm/amd/amdgpu/dce_virtual.c
@@ -659,12 +659,12 @@ static int dce_virtual_pageflip(struct amdgpu_device *adev,
    struct amdgpu_crtc *amdgpu_crtc;
    struct amdgpu_flip_work *works;

-   amdgpu_crtc = adev->mode_info.crtcs[crtc_id];
-
    if (crtc_id >= adev->mode_info.num_crtc) {
        DRM_ERROR("invalid pageflip crtc %d\n", crtc_id);
        return -EINVAL;
    }

+   amdgpu_crtc = adev->mode_info.crtcs[crtc_id];
+
    /* IRQ could occur when in initial stage */
    if (amdgpu_crtc == NULL)
        return 0;

Fix status (batch validation)

Applied with the 36 sibling fix.diffs of this batch in findings/poc/_batch41/all_fixes.patch; single in-guest make -j6 nativekernel KERNCONF=X86_64_GENERIC completed RC=0 with zero errors under -Werror (full log: findings/poc/_batch41/fix_build8.log). Behavior A/B intentionally skipped for this Info-tier item.

(No fix.diff for this item: documented recommendation only - see Fix above.)

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff not authored: nothing was verified at runtime; the one-line reorder in VERDICT.md is the recommended fix.

VERDICT.md contains the git-apply-able reorder diff.
per-fix-DF-4642

Confirmed kernel references

Detail

Evidence (decisive lines)

VERDICT.md (code excerpt + call-graph proof + clamp citations); README.md (why untested)

PoC changes

none β€” no PoC seed; finding is hardening-only, driver not loadable on guest

Verified recommended fix

Move the crtc_id >= num_crtc check above the crtcs[crtc_id] array read in dce_virtual_pageflip.

Verdict

Static proof only: dce_virtual.c:662 dereferences adev->mode_info.crtcs[crtc_id] two lines before the crtc_id bounds check at :664. Latent OOB read (CWE-125): the sole caller (dce_virtual.c:708) always passes a bounded crtc_id (num_crtc clamped 1..6 by amdgpu_device.c:1271-1275 against crtcs[AMDGPU_MAX_CRTCS=6]), so no OOB is reachable in the current call graph. Cannot be exercised on the audit guest: amdgpu is a kldload-only module and the guest has no AMD GPU. Reported as Info hardening (matches upstream Linux's inverted order).