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

Latent device-controlled-length memcpy in ena_com_get_hash_function(): keys_num (u32 in device-DMA buffer) << 2 as copy size

Summary

memcpy takes its length from the u32 keys_num field of the DMA-coherent control buffer the device fills - a malicious device yields an OOB read past the 40-byte key plus an OOB write into the caller's buffer (length up to ~16GiB). The write-side sibling is properly bounded. Dead code today (the only caller passes key==NULL) - filed as a porting landmine before anyone wires get_rss_key to sysctl/ethtool. Clamp fix.

Discussion (0)

No comments yet.

PoC verification

Evidence pack

findings/poc/DF-3938 Β· 6 files
FileTypeDescriptionSize
README.md β€” 2.0 KB ↓ raw
VERDICT.md β€” 2.1 KB ↓ raw
fix.diff β€” 420 B view raw
env.txt β€” 984 B view raw
manifest.json β€” 733 B view raw
verdict.json β€” 2.5 KB view raw

DF-3938 β€” ena_com_get_hash_function(): memcpy length = device-controlled u32 keys_num (latent OOB read+write; dead code in this port)

Finding

sys/dev/virtual/amazon/ena/ena-com/ena_com.c:2354:

    if (key)
        memcpy(key, hash_key->key, (size_t)(hash_key->keys_num) << 2);
  • hash_key is rss->hash_key β€” a DMA-coherent buffer (ena_com_hash_key_allocate, ena_com.c:966-980) that the DEVICE fills during ena_com_get_feature_ex(RSS_HASH_FUNCTION) (ena_com.c:2291-2295, indirect control-buffer DMA). keys_num is uint32_t (ena_admin_defs.h:593-595), so the copy length is a fully device-controlled keys_num << 2 (up to ~16 GiB) β€”
  • OOB read past the 48-byte hash_key allocation (key[10] = 40 bytes),
  • OOB write into the caller's key buffer, whose size is never passed.

  • sizeof(ena_admin_feature_rss_flow_hash_control.key) == 40, and the write-side sibling (ena_com_fill_hash_function, :2305-2313) correctly validates key_len <= sizeof(hash_key->key) β€” only the read side is unbounded.

Reachability today (honest): DEAD CODE

ena_com_get_hash_function has NO callers in this port: - ena.c never calls it (grep: only ena_com_fill_hash_function, ena_com_set_default_hash_ctrl at ena.c:3081/3088). - The only in-tree call site is the error-restore path inside ena_com_fill_hash_function itself (ena_com.c:2327) which passes key == NULL, so the memcpy is skipped (if (key)).

Filed Info (latent/hardening, pattern precedent DF-3799): the moment anyone wires RSS-key retrieval to a sysctl/ioctl (e.g. a future ena_sysctl rss_key handler, as FreeBSD's ena and Linux's ethtool get_rxfh_key do via a fixed 40/52-byte user buffer), a malicious/compromised ENA device (hypervisor) turns keys_num into an arbitrary-length kernel memcpy.

Fix

fix.diff clamps the length to sizeof(hash_key->key).

Why inconclusive on this guest

No ENA PCI function (env.txt); and the buggy line is unreachable even with one, until a caller passes a non-NULL key.

VERDICT.md
↓ download raw

DF-3938 β€” VERDICT

Status: inconclusive (dead code + hardware-gated) β€” defect certain at source; latent OOB read/write. Info severity.

Source proof

  • sys/dev/virtual/amazon/ena/ena-com/ena_admin_defs.h:593-599: struct ena_admin_feature_rss_flow_hash_control { uint32_t keys_num; uint32_t reserved; uint32_t key[10]; } β€” keys_num is a u32 inside the device-DMA'd control buffer.
  • ena_com.c:2342-2345 β€” ena_com_get_feature_ex(..., rss->hash_key_dma_addr, sizeof(*rss->hash_key)): the device writes the whole 48-byte struct (keys_num included) by DMA.
  • ena_com.c:2354 β€” memcpy(key, hash_key->key, (size_t)(hash_key->keys_num) << 2); β€” length device-chosen, up to 0x3FFFFFFFC bytes; source region is 40 bytes inside a 48-byte coherent alloc; destination size unknown to this function. Both directions unbounded.
  • Contrast with the bounded write side: ena_com.c:2305-2313 (key_len > sizeof(hash_key->key) check before memcpy(hash_key->key, key, key_len)).

Reachability (killed honestly)

  • Whole-tree grep for callers: ena_com_get_hash_function appears only at its definition (ena_com.c:2332), its header prototype (ena_com.h:715), and one internal call with key == NULL (ena_com.c:2327, error-restore inside ena_com_fill_hash_function). ena.c / ena_sysctl.c never call it. So the OOB cannot execute in this tree today β€” hence Info/latent, not Medium.
  • Even with hardware present, the guest has no ENA device (env.txt).

Classification: Info, confidence certain (that the code is wrong), latent β€” the DF-3799 precedent (dead-code defect documented for the port's future).

Fix

fix.diff: clamp to sizeof(hash_key->key) using ENA_MIN32 (already defined in ena_plat.h).

fix_status

not_testable β€” dead code + no hardware.

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 not validated: the buggy line is dead code in this port and requires ENA hardware regardless. fix.diff clamps the memcpy length to sizeof(hash_key->key) via ENA_MIN32 (defined ena_plat.h:196); authored post-verification.

findings/poc/DF-3938/fix.diff + VERDICT.md
↓ fix.diffper-fix-DF-3938

Confirmed kernel references

Detail

Exploit chain

future caller with fixed-size key buffer (ethtool/sysctl get_rss_key style) + malicious ENA device writing keys_num > 10 -> arbitrary-length kernel memcpy -> OOB read of coherent DMA memory + OOB write into caller buffer

Evidence (decisive lines)

findings/poc/DF-3938/VERDICT.md (caller grep) + fix.diff + env.txt

PoC changes

No seed PoC; dead code + hardware-gated.

Verified recommended fix

Clamp the copy length to sizeof(hash_key->key) (fix.diff).

Verdict

ena_com_get_hash_function copies the RSS key to the caller with a fully device-controlled length: memcpy(key, hash_key->key, (size_t)(hash_key->keys_num) << 2) at ena_com.c:2354, where keys_num is a u32 living in the DMA-coherent control buffer the device fills during get_feature (ena_admin_defs.h:593-595, ena_com.c:2291-2345). A malicious ENA device/hypervisor choosing keys_num yields an OOB read past the 48-byte hash_key alloc plus an OOB write of the caller's buffer (length up to ~16GiB). Reachability honestly killed: no caller passes a non-NULL key anywhere in the tree (only the internal restore call at ena_com.c:2327 passes NULL; ena.c/ena_sysctl.c never call the function) β€” latent, DF-3799-style. Info/hardening; clamp with fix.diff before any future ethtool/sysctl wiring.