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

intel_hdcp_validate_v_prime KSV fill shift missing -sha_leftovers offset: repeater SHA-1 input stream corrupted for >=2 downstream devices, HDCP1.4 repeater authentication always fails (fail-closed)

Summary

The fill loop packs KSV bytes into sha_text without offsetting for the sha_leftovers bytes already parked in those slots by the previous iteration - from the second downstream KSV onward every byte lands one slot too high and a zero fills the LSB slot, so the hashed stream diverges at byte 5 for any >=2-device repeater topology. Upstream fixed exactly this in the v5.17 rework; DF carries the pre-fix form (verified identical to v4.17..v5.15-era). Fail-closed: V' never matches, repeater auth never succeeds - unencrypted output while userspace believes protection was requested. No memory-safety impact, no bypass. Guest-compiled simulation of both loop forms shows the exact divergence (byte 5 = leftover-OR collision) vs the fixed exact concatenation. Offset-the-shift fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-4577 Β· 11 files
FileTypeDescriptionSize
README.md β€” 3.6 KB ↓ raw
VERDICT.md β€” 4.0 KB ↓ raw
df4577_sim.c β€” 2.8 KB view raw
build.sh β€” 65 B view raw
run.sh β€” 89 B view raw
build.log β€” 61 B view raw
run.log β€” 1.5 KB view raw
env.txt β€” 367 B view raw
fix.diff β€” 699 B view raw
manifest.json β€” 1.6 KB view raw
verdict.json β€” 4.0 KB view raw

DF-4577 — intel_hdcp.c KSV→SHA_TEXT fill offset bug (fail-closed HDCP repeater auth)

What this is

intel_hdcp_validate_v_prime() (sys/dev/drm/i915/intel_hdcp.c:216-254) feeds the concatenated 5-byte KSVs to the HDCP repeater SHA-1 engine 32 bits at a time via HDCP_SHA_TEXT. The fill loop

sha_empty = sizeof(sha_text) - sha_leftovers;
for (j = 0; j < sha_empty; j++)
        sha_text |= ksv[j] << ((sizeof(sha_text) - j - 1) * 8);   /* DF/sys */

computes each new byte's shift slot without offsetting for the leftover byte(s) already parked in the high slots of sha_text (parked there by the leftover loop at intel_hdcp.c:237-239). On the second and subsequent KSVs, the first fill byte ksv[0] is OR-ed into the same slot occupied by the previous KSV's leftover byte, and the remaining bytes land one slot too high, with a zero in the low slot. The byte stream hashed by the engine therefore diverges from the required KSV[0]||KSV[1]||… concatenation at the first 4-byte boundary (stream byte 5).

Upstream fixed this in the v5.17 HDCP rework (drivers/gpu/drm/i915/display/ intel_hdcp.c:527-529, (sizeof(sha_text) - j - 1 - sha_leftovers) * 8); DF carries the pre-v5.17 form (verified identical to Linux v4.17/v4.18/v5.4/ v5.15-era code).

Impact

  • The hardware SHA-1 over the KSV list can never match the sink's V' when the repeater reports β‰₯ 2 downstream devices (N=1 is correct: its single leftover byte is consumed by the BSTATUS tail paths at intel_hdcp.c:262-360).
  • Result: intel_hdcp_validate_v_prime() β†’ -ENXIO (3 attempts) β†’ intel_hdcp_auth_downstream() fails β†’ _intel_hdcp_enable() fails all 3 auth tries β†’ intel_hdcp_enable() returns error β†’ the Content Protection property never reaches ENABLED. Fail-closed: HDCP1.4 content protection simply cannot be enabled behind a multi-device repeater on this driver. No kernel memory-safety impact; no authentication bypass (a wrong hash can only fail the V' comparison, never pass it).

Proof (arithmetic simulation, guest-compiled)

df4577_sim.c reproduces both the DF form and the upstream-fixed form exactly and prints the byte stream each writes to HDCP_SHA_TEXT. Run on the audit QEMU guest (DragonFly 6.5-DEVELOPMENT x86_64):

./build.sh && ./run.sh        # or see run.log for the captured output

Decisive lines (N=2, stream bytes are 1..10, divergence bolded in run.log):

required   :  1  2  3  4  5  6  7  8  9 10
DF (old)   :  1  2  3  4  7  7  8  0        <- byte5 = 5|6 = 7, byte6.. shifted
upstream   :  1  2  3  4  5  6  7  8        <- exact concatenation

7 = 5|6 β€” the leftover byte 5 OR-ed with the next KSV's first byte 6.

Why no kernel-level PoC (status: untested)

Triggering in-kernel requires: Intel HSW/gen8+ display hardware (not present in the QEMU guest), a connected HDCP repeater reporting β‰₯2 downstream devices, an enabled CRTC, and a DRM master in group video (DRM_IOCTL_MODE_OBJ_SETPROPERTY is DRM_MASTER-gated, sys/dev/drm/drm_ioctl.c:679; /dev/dri/card* is root:video 0660, etc/defaults/devfs.conf:7). Low-severity, fail-closed, hardware-gated β†’ kernel verification skipped per audit policy; the arithmetic proof above is conclusive for the root cause.

Fix

fix.diff β€” one line, matches upstream v5.17+:

-   sha_text |= ksv[j] << ((sizeof(sha_text) - j - 1) * 8);
+   sha_text |= ksv[j] << ((sizeof(sha_text) - j - 1 - sha_leftovers) * 8);

Fix validation at kernel level is not possible on this guest (no i915-class hardware); the same simulation exercises the fixed expression and emits the exact required stream (see run.log "upstream" rows).

VERDICT.md
↓ download raw

DF-4577 β€” VERDICT (pass 2, df-bsd-reviewer)

Status: untested (kernel level) / analytically certain (arithmetic proof) Impact: none on kernel memory safety; fail-closed loss of HDCP1.4 repeater authentication (content-protection feature cannot engage for topologies with >= 2 downstream devices).

Root cause (path:line)

sys/dev/drm/i915/intel_hdcp.c:221-223 β€” the KSV fill loop parks each new byte at slot (sizeof(sha_text) - j - 1) * 8, ignoring the sha_leftovers bytes already parked in those top slots by intel_hdcp.c:237-239 during the previous iteration. From the second KSV on, ksv[0] OR-collides with the parked leftover byte and every following byte lands one slot too high (zero in the LSB slot). The HDCP_SHA_TEXT byte stream therefore diverges from the required KSV concatenation at stream byte 5 for any topology with >= 2 downstream devices.

Upstream comparison (fetched from torvalds/linux during this audit): v4.17, v4.18, v5.4, v5.15-era: identical buggy form v5.17+ (HDCP rework, drivers/gpu/drm/i915/display/intel_hdcp.c:527-529): u8 off = ((sizeof(sha_text) - j - 1 - sha_leftovers) * 8); β€” FIXED DF's tree carries the pre-fix form.

Why fail-closed

The corrupted stream is hashed by the display engine and compared against the sink-supplied V' (intel_hdcp.c:383-392). A wrong hash can only produce a mismatch (HDCP_SHA1_V_MATCH clear) -> -ENXIO, never a false pass: intel_hdcp_validate_v_prime (-ENXIO x3) -> intel_hdcp_auth_downstream err (intel_hdcp.c:452-455) -> _intel_hdcp_enable fails all 3 tries (intel_hdcp.c:679-692) -> intel_hdcp_enable returns error, property stays DESIRED No bypass, no escalation, no memory unsafety. Security consequence is the unavailability of the content-protection mechanism on repeater topologies.

Evidence

  • findings/poc/DF-4577/df4577_sim.c β€” faithful simulation of both forms
  • findings/poc/DF-4577/run.log β€” output compiled+run on the audit guest (DragonFly 6.5-DEVELOPMENT x86_64, gcc): DF form diverges at byte 5 (5|6 = 7) for N>=2; upstream form emits the exact concatenation 1..20 for N=5; N=1 correct in both (leftover consumed by the BSTATUS tail).
  • Upstream fix reference: torvalds/linux v5.17+ drivers/gpu/drm/i915/display/intel_hdcp.c:529.

Verification status

Kernel-level reproduction is hardware-gated (needs Intel HSW/gen8+ display, an HDCP repeater sink with >=2 downstream devices, an enabled CRTC, and a DRM_MASTER fd in group video β€” DRM_IOCTL_MODE_OBJ_SETPROPERTY is DRM_MASTER-gated at sys/dev/drm/drm_ioctl.c:679; /dev/dri/card* is root:video 0660 per etc/defaults/devfs.conf:7). The QEMU audit guest has no i915-class GPU, so status is "untested" for the kernel path. The arithmetic proof is conclusive for the root cause, and fix.diff is exactly the upstream v5.17 correction (validated by the same simulation).

Negative results recorded during this pass (context)

  • Repeater KSV FIFO sizing: HDMI shim reads exactly N*5 bytes (intel_hdmi.c:1048-1059), DP shim 15-byte windows bounded by min() (intel_dp.c:5482-5493); num_downstream masked to 7 bits (drm_hdcp.h:22) -> kcalloc(5, <=127) with exact fill β€” the "classic" overflow does NOT exist here.
  • intel_hdcp_atomic_check NULL-crtc-state: killed β€” drm_atomic_get_connector_state allocates the CRTC state (drm_atomic.c, tail of function) whenever the connector keeps its CRTC, so intel_hdcp.c:824 cannot deref NULL.
  • Work lifetime: hdcp_check_work/hdcp_prop_work cancelled before connector destruction at unload (intel_display.c:16175-16177, before drm_mode_config_cleanup).
  • bksv/an/ri/vprime unions fully initialized before shim reads (intel_hdcp.c:508-528, 584).

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.

Fix verification

not_testable
baseline no→ patch + rebuild →patched clean

fix.diff is the exact upstream v5.17 correction; validated by the same arithmetic simulation (the fixed form emits the exact required byte stream for all N). A single-fix kernel build + on-hardware validation is impossible on this guest (no Intel gen8+ display hardware), hence not_testable.

findings/poc/DF-4577/fix.diff (one-line change at sys/dev/drm/i915/intel_hdcp.c:223); run.log 'upstream' rows show the corrected expression producing the exact concatenation 1..20 for N=5
↓ fix.diffper-fix-DF-4577

Confirmed kernel references

Detail

Evidence (decisive lines)

findings/poc/DF-4577/run.log (decisive: 'first divergence at byte 5: got 7 want 5' for N>=2; upstream row exact), df4577_sim.c (faithful simulation of both loop forms), fix.diff (one-line upstream-aligned fix), VERDICT.md (full analysis + negative results)

PoC changes

No seed PoC existed (pass-2 new finding); authored df4577_sim.c from scratch β€” a userspace arithmetic simulation of the exact fill/leftover loops of both the DF and upstream-fixed forms, compiled and run on the DragonFly audit guest.

Verified recommended fix

Offset the fill shift by the parked leftovers, matching upstream v5.17+: sha_text |= ksv[j] << ((sizeof(sha_text) - j - 1 - sha_leftovers) * 8); (see fix.diff)

Verdict

intel_hdcp_validate_v_prime (sys/dev/drm/i915/intel_hdcp.c:221-223) computes the KSV fill shift without offsetting for sha_leftovers, so from the second downstream KSV onward the new bytes OR-collide with the parked leftover byte and shift one slot high: the byte stream hashed by HDCP_SHA_TEXT diverges from the required KSV concatenation at stream byte 5. Proven mechanically by a guest-compiled simulation of both forms (findings/poc/DF-4577/run.log: N=2 DF row '1 2 3 4 7 7 8 0' vs required '1 2 3 4 5 6 7 8', where 7 = 5|6) and by diff against upstream, which fixed exactly this in the v5.17 HDCP rework ((sizeof(sha_text) - j - 1 - sha_leftovers) * 8). Impact is fail-closed: V' can never match for repeaters reporting >=2 downstream devices, so HDCP1.4 repeater authentication always fails and content protection cannot be enabled on such topologies. No kernel memory-safety impact and no bypass (a wrong hash can only fail the V' comparison, never pass it). Kernel-level reproduction is hardware-gated (Intel HSW/gen8+ display + HDCP repeater sink + DRM_MASTER fd in group video) and the QEMU audit guest has no i915-class GPU, so the kernel path is untested; the root cause and the fix are analytically certain.