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)
PoC verification
Evidence pack
findings/poc/DF-3938 Β· 6 files| File | Type | Description | Size | |
|---|---|---|---|---|
| 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_keyisrss->hash_keyβ a DMA-coherent buffer (ena_com_hash_key_allocate,ena_com.c:966-980) that the DEVICE fills duringena_com_get_feature_ex(RSS_HASH_FUNCTION)(ena_com.c:2291-2295, indirect control-buffer DMA).keys_numisuint32_t(ena_admin_defs.h:593-595), so the copy length is a fully device-controlledkeys_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
keybuffer, 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 validateskey_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.
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_numis 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 beforememcpy(hash_key->key, key, key_len)).
Reachability (killed honestly)
- Whole-tree grep for callers:
ena_com_get_hash_functionappears only at its definition (ena_com.c:2332), its header prototype (ena_com.h:715), and one internal call withkey == NULL(ena_com.c:2327, error-restore insideena_com_fill_hash_function).ena.c/ena_sysctl.cnever 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_testableFix 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
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.
No comments yet.