https://bugs.dpdk.org/show_bug.cgi?id=2102
Bug ID: 2102
Summary: net/ice: ice_get_module_eeprom() can read the wrong
page when a block straddles a 256-byte boundary
Product: DPDK
Version: unspecified
Hardware: All
OS: All
Status: UNCONFIRMED
Severity: normal
Priority: Normal
Component: ethdev
Assignee: [email protected]
Reporter: [email protected]
Target Milestone: ---
https://github.com/DPDK/dpdk/blob/a13c1053360ee26bbacca9f883d3f3cb83a0f366/drivers/net/intel/ice/ice_ethdev.c#L6683-L6772
for (i = 0; i < info->length; i += SFF_READ_BLOCK_SIZE) {
offset = i + info->offset;
page = 0;
/* Check if we need to access the other memory page */
if (is_sfp) {
if (offset >= ICE_MODULE_SFF_8079_LEN) {
offset -= ICE_MODULE_SFF_8079_LEN;
addr = ICE_I2C_EEPROM_DEV_ADDR2;
}
} else {
while (offset >= ICE_MODULE_SFF_8436_LEN) {
offset -= ICE_MODULE_SFF_8436_LEN / 2;
page++;
}
}
...
status = ice_aq_sff_eeprom(hw, 0, addr, offset, page, !is_sfp, value,
SFF_READ_BLOCK_SIZE, 0, NULL);
...
if ((i + SFF_READ_BLOCK_SIZE) <= info->length)
memcpy(data + i, value, SFF_READ_BLOCK_SIZE);
}
The page/address switch is computed once per 8-byte block, from that
block's starting offset only. Nothing checks whether offset +
SFF_READ_BLOCK_SIZE actually stays within the same page/address before
issuing the read, so a block that starts just below a boundary is
still issued as a single read against that one address/page -- the
bytes before the boundary come back correct, but the trailing bytes
past it are read from whatever happens to sit at that same local
offset on the wrong side, and that wrong data is copied into the
caller's buffer along with the rest.
1. SFP (is_sfp branch): A0h/A2h straddle, and past A2h's own end.
A call with info->offset=253, info->length=8 has its one block at
offset=253, which is < 256 so it's read entirely from A0h, bytes
253-260 -- but bytes 256-260 actually belong to A2h. That wrong data
is copied to the caller.
The same kind of gap exists at A2h's own end: info->offset=505,
info->length=8 computes offset=505-256=249 on A2h, reading local
A2h bytes 249-256 -- byte 256 is past A2h's own 256-byte range.
2. QSFP (else branch): wrong page for the straddling tail bytes.
A call with info->offset=250, info->length=8 has its one block at
offset=250, page=0 (since 250 < 256), reading bytes 250-257 -- but
bytes 256-257 belong to the next page. That wrong data is copied to
the caller. The same gap recurs at every subsequent page boundary.
Likely consequence in all cases (not empirically tested): wrong byte
values returned to the caller under a fully successful status, for
any read whose block happens to straddle one of these boundaries.
What happens for an offset far past any valid range differs between
the two branches:
- SFP: info->offset=600 computes offset=600-256=344 (only one
subtraction, no loop) and addr=A2h. 344 (0x158) fails the
mem_addr & 0xff00 check in ice_aq_sff_eeprom() below, so this case
at least returns an error (ICE_ERR_PARAM) instead of silently
reading garbage.
- QSFP: the while loop keeps subtracting 128 and incrementing page
for as long as offset >= 256, with no upper bound on page. There is
no equivalent range check on page anywhere -- the result of asking
for a page number the module doesn't implement is undefined from
the driver's side and would depend on the module's own hardware
behavior.
https://github.com/DPDK/dpdk/blob/a13c1053360ee26bbacca9f883d3f3cb83a0f366/drivers/net/intel/ice/base/ice_common.c#L4350-L4379
int
ice_aq_sff_eeprom(struct ice_hw *hw, u16 lport, u8 bus_addr,
u16 mem_addr, u8 page, u8 set_page, u8 *data, u8 length,
bool write, struct ice_sq_cd *cd)
{
...
if (!data || (mem_addr & 0xff00))
return ICE_ERR_PARAM;
...
}
ice_aq_sff_eeprom() validates the starting mem_addr but never checks
mem_addr + length against the page boundary, and never validates page
at all.
Suggested fix:
Hardening ice_aq_sff_eeprom() to also reject mem_addr + length >
0xff would catch some out-of-range cases earlier, but that alone is
not a fix and must not be the only change: ice_get_module_eeprom()
always requests a fixed SFF_READ_BLOCK_SIZE (8) bytes regardless of
how much of that block the caller actually needs, so a legitimate
small request (e.g. offset=250, length=4) would start failing outright
once such a check is added, even though it never needed the
straddling bytes in the first place.
The required fix belongs in ice_get_module_eeprom() itself. A single
I2C/admin-queue transaction cannot cross either boundary correctly in
the first place -- SFP's A0h and A2h are distinct I2C device
addresses, and QSFP's pages reuse the same address range with
different contents depending on which page was last selected, so a
straddling block was never going to be satisfiable as one read
regardless of any driver change. The fix is to check whether offset +
SFF_READ_BLOCK_SIZE crosses the relevant boundary and, if so, split
that iteration into two correctly-addressed sub-reads (switching
device address for SFP, or writing the new page-select value for
QSFP) instead of issuing one read spanning both. Each individual read
should also be sized to min(SFF_READ_BLOCK_SIZE, info->length - i)
rather than always requesting a full fixed 8 bytes, so a small
trailing request doesn't over-read into the next page for data nobody
asked for.
With that in place, adding the mem_addr + length check to
ice_aq_sff_eeprom() becomes reasonable defense-in-depth against any
other/future caller of that function, but it is not a substitute for
the ice_get_module_eeprom() fix above.
--
You are receiving this mail because:
You are the assignee for the bug.