hw/net/opencores_eth: guest MII register selector reads past local PHY register array

Disclosure note: This was found during a local QEMU device-audit campaign that used automation, source review, and LLM assistance. I manually triaged the issue and reproduced it with the qtest/runtime evidence below. I am filing it as confidential because it is a host-side QEMU memory-safety issue.

Summary

The OpenCores Ethernet local PHY emulation accepts a 5-bit MII register selector, but its local Mii.regs array has only 16 entries. The write helper guards the index, while the read helper does not.

When the guest programs MIIADDRESS.RGAD to 16 or higher and then issues MIICOMMAND_RSTAT, open_eth_mii_command_host_write() passes the guest-controlled selector to mii_read_host(). That helper evaluates s->regs[idx] in both the trace call and the return path without an idx < MII_REG_MAX check, producing QEMU-process out-of-bounds reads past uint16_t regs[16].

The validated qtest uses the lx60 board with -net nic,model=open_eth and triggers the first invalid selector value, RGAD == 16.

Tested version / environment

  • QEMU source tree: local upstream tree at commit 20553466cc47af6a8c95f665b601fce3c852e503
  • QEMU version reported by build: 11.0.50
  • Host: x86_64 Linux
  • Target binary: xtensa-softmmu UBSan/ASan build
  • Build: xtensa-softmmu with AddressSanitizer and UndefinedBehaviorSanitizer enabled
  • Machine/device route: -machine lx60 -net nic,model=open_eth -qtest stdio

Affected code

  • hw/net/opencores_eth.c:62-70: MII_REG_MAX is 16 and Mii stores uint16_t regs[MII_REG_MAX].
  • hw/net/opencores_eth.c:112-128: mii_write_host() checks idx < MII_REG_MAX before tracing or writing.
  • hw/net/opencores_eth.c:131-134: mii_read_host() uses s->regs[idx] in both the trace call and return value without an index guard.
  • hw/net/opencores_eth.c:195-199: MIIADDRESS.RGAD is a 5-bit field, so guests can encode values 0..31.
  • hw/net/opencores_eth.c:631-650: open_eth_mii_command_host_write() extracts RGAD and calls mii_read_host(&s->mii, rgad) when MIICOMMAND_RSTAT and FIAD == DEFAULT_PHY are set.
  • hw/net/opencores_eth.c:662-683: open_eth_reg_write() stores MIIADDRESS after only validating the register offset.

Trigger

The guest controls the OpenCores Ethernet MMIO register writes. The validated qtest does:

  1. Write MIIADDRESS with FIAD == DEFAULT_PHY and RGAD == 16.
  2. Write MIICOMMAND with MIICOMMAND_RSTAT.

The concrete commands used were:

  • writel 0xfd030030 0x00001001
  • writel 0xfd03002c 0x00000002

This selects the default PHY and the first out-of-range register index, then triggers the MII read command.

Security boundary / triggerability

This is triggered through the emulated device MMIO interface. A guest that can program the OpenCores Ethernet controller registers can control MIIADDRESS.RGAD and then request a PHY register read. I am filing confidentially because the guest-controlled selector reaches a host-side fixed-array read without a bounds check.

Faulting access

The faulting host reads are the two evaluations of s->regs[idx] in mii_read_host():

  • the trace argument trace_open_eth_mii_read(idx, s->regs[idx])
  • the return value return s->regs[idx]

For idx >= 16, both reads are out of bounds for uint16_t regs[16].

Reproduction

Attached reproducer: opencores_mii_register_read_qtest.sh

Run the script against an instrumented xtensa-softmmu binary:

./opencores_mii_register_read_qtest.sh ./build/qemu-system-xtensa \
> opencores_mii_register_read.trace.txt \
2> opencores_mii_register_read.log.txt

The script starts QEMU with:

qemu-system-xtensa
-machine lx60
-net nic,model=open_eth
-qtest stdio
-trace open_eth_mii_read

It then writes the two MMIO registers needed to reach the MII read helper.

Runtime evidence

Attached logs: opencores_mii_register_read.log.txt and opencores_mii_register_read.trace.txt

Key runtime evidence:

[R +0.005165] writel 0xfd030030 0x00001001
[R +0.005236] writel 0xfd03002c 0x00000002
../hw/net/opencores_eth.c:133:41: runtime error: index 16 out of bounds for type 'uint16_t [16]'
open_eth_mii_read MII[0x10] -> 0x0001
../hw/net/opencores_eth.c:134:19: runtime error: index 16 out of bounds for type 'uint16_t [16]'

The qtest wrapper itself exits through the outer timeout because qtest stdio has no quit command, but the out-of-bounds evidence is emitted before timeout and the trace confirms the helper was reached.

Triage notes

  • Register MMIO bounds do not refute the issue: they constrain which controller register is written, not the stored RGAD field value.
  • Access-size constraints do not refute the issue: the validated path uses normal 32-bit MMIO writes matching open_eth_reg_ops.
  • The read path is distinct from the descriptor-window MMIO image and from guest-RAM DMA.
  • FIAD == DEFAULT_PHY is guest-satisfiable; the validated qtest used FIAD == 1.
  • The write helper already shows the intended boundary policy by rejecting idx >= MII_REG_MAX.

Impact

A guest with access to the emulated OpenCores Ethernet controller can trigger a host QEMU out-of-bounds read from memory adjacent to the local PHY register array. I have not proven guest-visible disclosure or control-flow impact.

Suggested fix direction

Apply the same boundary policy used by mii_write_host() to mii_read_host().

Conservative options include:

  • return a default PHY value such as 0xffff for idx >= MII_REG_MAX;
  • guard before both the trace and return sites so s->regs[idx] is never evaluated out of range;
  • add a qtest that programs RGAD == 16 and verifies the access is rejected or handled safely under instrumentation.

Attachments