Component: r_ether (Ethernet/EDMAC driver)
FSP version: 5.3.0 (Built with Renesas Advanced Flexible Software Package version 5.3.0)
Device family verified against: RA6M3 (R01UH0886EJ0110 Rev.1.10), EDMAC §31
Severity: low–moderate - read-only out-of-bounds source read of ≤3 bytes and an
incorrect reported frame length, reachable in a supported-looking configuration
(buffers smaller than the max on-wire frame + ETHER_PADDING_* enabled).
Summary
R_ETHER_Read computes the number of bytes to hand back / memcpy as
descriptor->size + cfg->padding, unconditionally, for every receive
descriptor it consumes:
/* r_ether.c (R_ETHER_Read) */
received_size =
(uint32_t) (p_instance_ctrl->p_rx_descriptor->size +
(uint16_t) p_instance_ctrl->p_ether_cfg->padding);
...
if (ETHER_ZEROCOPY_DISABLE == cfg->zerocopy) {
memcpy(p_buffer, p_read_buffer, received_size); /* source = RX buffer */
R_ETHER_BufferRelease(...);
}
*length_bytes = received_size;
Both descriptor->size (RD1.RFL) and cfg->padding (RPADIR.PADS) are only
well-behaved for the single-buffer case (one buffer holds the whole frame,
RD0.RFP = 11b). When a frame spans multiple buffers - which the EDMAC does
whenever the frame is longer than a descriptor's RBL - this arithmetic is wrong
on the non-11b fragments.
Hardware background (RA6M3 manual, EDMAC §31)
- RD1.RFL is "the length … of the receive frame stored in the buffer. This
does not include the number of bytes for padding set in the RPADIR
register. These bits are written back to the descriptor associated with the
end of a frame." (p.932)
- §31.3.3 Reception (p.934): when the buffer becomes full mid-frame the
EDMAC writes RFP = 10b/00b, RACT = 0, and does not write RFL; only at
frame completion does it write RFP = 11b/01b, RACT = 0, and RFL.
- RD0.RFP[1:0] (p.931):
00 middle, 01 end, 10 head, 11 all. So bit1 =
"this buffer contains the frame's head", bit0 = "contains the end". RFL is
valid only on bit0-set fragments (01/11).
- RPADIR (p.924):
PADS = 0..3 zero pad bytes inserted at frame offset
PADR (0..63, i.e. within the first 64 bytes of the frame). The manual
describes this insertion only in single-buffer terms and is silent on what
happens when the insertion point falls near or past a buffer boundary.
The defect, by fragment type
For a frame split across N buffers, R_ETHER_Read is called once per fragment
(each BufferRelease advances p_rx_descriptor). With padding != 0:
-
End fragment (RFP = 01b) - RFL is written (valid = tail frame bytes in
this buffer, excludes padding). But the padding was inserted near the head,
which is in an earlier buffer - there is no padding in the end buffer. FSP
still adds PADS, so:
received_size = RFL + PADS over-counts by PADS.
- In non-zerocopy mode,
memcpy(dst, src, RFL + PADS) reads up to PADS
(≤3) bytes past the valid data in the RX buffer (read-only over-read into
whatever follows the buffer in SRAM, copied into the destination tail).
- The reported
*length_bytes is too large by PADS.
-
Head / middle fragments (RFP = 10b / 00b) - RFL is not written
(§31.3.3), so descriptor->size is stale (init 0, or a prior frame's RFL
at this descriptor). received_size = stale + PADS is garbage: either far too
small (fresh 0 → returns PADS bytes) or, with a large stale value, far too
large → a much bigger memcpy source over-read and wrong length.
-
Padding-straddle / padding-in-later-buffer corner (tiny buffers). When the
per-buffer size S is small enough that the insertion offset reaches or crosses
the boundary, the pad bytes physically land in a non-head buffer. Two
sub-cases, which the silicon may even handle differently and the manual does not
cover:
- Straddle (
PADR < S < PADR+PADS): pads split - S−PADR in the head
buffer, the rest in the next. E.g. S=64, PADR=63, PADS=3 → 1 pad byte in
buffer 1, 2 in buffer 2.
- Entirely in a later buffer (
PADR ≥ S): no pads in the head at all. Only
reachable at S = 32 (since PADR ≤ 63). E.g. S=32, PADR=40, PADS=3 → all
3 pad bytes are in buffer 2.
Here a 2-buffer frame can put padding into the end fragment (RFP = 01b,
valid RFL), so the correct term is "pad bytes physically in this buffer"
= PADS − max(0, S − PADR) clamped to [0, PADS] - not a flat PADS and not
a flat 0. This makes even a "add padding only on the head fragment" fix
insufficient in this corner.
Realistic trigger (does not require exotic tiny buffers)
ETHER_ETHERC_RFLR_DEFAULT_VALUE hardcodes the on-wire frame limit to 1518.
Pick any buffer size below that, e.g. a pool of ether_buffer_size = 576 (a
common choice to bound per-frame RAM) with ETHER_ZEROCOPY_DISABLE and
ETHER_PADDING_2BYTE for IP-header alignment. A normal 1500-byte frame then
splits across 3 buffers (576 + 576 + 348). On the end fragment FSP memcpys
RFL + 2 bytes and over-reads the RX buffer by 2 bytes; the head/middle fragments
return stale-RFL garbage lengths. No jumbo frames or S ≤ 64 - just buffers
smaller than the MTU plus padding, which is a configuration the API otherwise
appears to support.
Splitting is unconditional silicon behavior - RD0.RACT clears "when the receive
buffer becomes full" (p.932), i.e. when the single frame currently being
written into that buffer reaches RBL before it ends (§31.3.3: "if the data
length of the received frame is longer than the buffer length … when the receive
buffer becomes full"). There is no "single buffer / truncate" mode. And
R_ETHER_Read never inspects RD0.RFP[1:0] (it gates only on RACT, RFS7_RMAF, and
RFE - L901-L940), so it returns every consumed fragment, head/middle/end
alike, one per call. With padding != 0 the received_size > 0 data gate
(ETHER_NO_DATA = 0, L129) is always satisfied, so each buggy fragment is in fact
surfaced to the caller - the defect is reached on every fragment of every split
frame, not just occasionally.
Impact
- Memory safety: read-only source over-read of up to
PADS (≤3) bytes per
end fragment (non-zerocopy). With a large stale RFL on a head/middle fragment
the over-read can be much larger. No write overflow of the destination iff the
caller sized it ≥ buffer_size + padding; the API does not document that
requirement, so a caller sizing the destination to buffer_size (the obvious
choice) gets a 1–PADS-byte destination overflow.
- Correctness: split frames are returned with wrong per-fragment lengths, so
they cannot be reassembled correctly through this API. (Zerocopy avoids the
memcpy/over-read but the length is still wrong, leading to over-reads down the line.)
Notes / open question for Renesas
The RPADIR section does not specify the multi-buffer interaction. I am unsure about the silicon behavior when the padding insertion point lands at/after a buffer boundary (straddle vs. entirely-next-buffer above). A whole-frame vs. per-fragment definition of RFL on the 01b end descriptor (the field def says "stored in the buffer" = per-fragment; §31.3.3 says "the receive frame length") would also be worth clarifying in the manual.
References
Component:
r_ether(Ethernet/EDMAC driver)FSP version: 5.3.0 (
Built with Renesas Advanced Flexible Software Package version 5.3.0)Device family verified against: RA6M3 (
R01UH0886EJ0110Rev.1.10), EDMAC §31Severity: low–moderate - read-only out-of-bounds source read of ≤3 bytes and an
incorrect reported frame length, reachable in a supported-looking configuration
(buffers smaller than the max on-wire frame +
ETHER_PADDING_*enabled).Summary
R_ETHER_Readcomputes the number of bytes to hand back /memcpyasdescriptor->size + cfg->padding, unconditionally, for every receivedescriptor it consumes:
memcpy)*length_bytes)Both
descriptor->size(RD1.RFL) andcfg->padding(RPADIR.PADS) are onlywell-behaved for the single-buffer case (one buffer holds the whole frame,
RD0.RFP =
11b). When a frame spans multiple buffers - which the EDMAC doeswhenever the frame is longer than a descriptor's RBL - this arithmetic is wrong
on the non-
11bfragments.Hardware background (RA6M3 manual, EDMAC §31)
does not include the number of bytes for padding set in the RPADIR
register. These bits are written back to the descriptor associated with the
end of a frame." (p.932)
EDMAC writes
RFP = 10b/00b,RACT = 0, and does not write RFL; only atframe completion does it write
RFP = 11b/01b,RACT = 0, and RFL.00middle,01end,10head,11all. So bit1 ="this buffer contains the frame's head", bit0 = "contains the end". RFL is
valid only on bit0-set fragments (
01/11).PADS= 0..3 zero pad bytes inserted at frame offsetPADR(0..63, i.e. within the first 64 bytes of the frame). The manualdescribes this insertion only in single-buffer terms and is silent on what
happens when the insertion point falls near or past a buffer boundary.
The defect, by fragment type
For a frame split across N buffers,
R_ETHER_Readis called once per fragment(each
BufferReleaseadvancesp_rx_descriptor). Withpadding != 0:End fragment (
RFP = 01b) - RFL is written (valid = tail frame bytes inthis buffer, excludes padding). But the padding was inserted near the head,
which is in an earlier buffer - there is no padding in the end buffer. FSP
still adds
PADS, so:received_size = RFL + PADSover-counts byPADS.memcpy(dst, src, RFL + PADS)reads up toPADS(≤3) bytes past the valid data in the RX buffer (read-only over-read into
whatever follows the buffer in SRAM, copied into the destination tail).
*length_bytesis too large byPADS.Head / middle fragments (
RFP = 10b/00b) - RFL is not written(§31.3.3), so
descriptor->sizeis stale (init 0, or a prior frame's RFLat this descriptor).
received_size = stale + PADSis garbage: either far toosmall (fresh 0 → returns
PADSbytes) or, with a large stale value, far toolarge → a much bigger
memcpysource over-read and wrong length.Padding-straddle / padding-in-later-buffer corner (tiny buffers). When the
per-buffer size
Sis small enough that the insertion offset reaches or crossesthe boundary, the pad bytes physically land in a non-head buffer. Two
sub-cases, which the silicon may even handle differently and the manual does not
cover:
PADR < S < PADR+PADS): pads split -S−PADRin the headbuffer, the rest in the next. E.g.
S=64, PADR=63, PADS=3→ 1 pad byte inbuffer 1, 2 in buffer 2.
PADR ≥ S): no pads in the head at all. Onlyreachable at
S = 32(sincePADR ≤ 63). E.g.S=32, PADR=40, PADS=3→ all3 pad bytes are in buffer 2.
Here a 2-buffer frame can put padding into the end fragment (
RFP = 01b,valid RFL), so the correct term is "pad bytes physically in this buffer"
=
PADS − max(0, S − PADR)clamped to[0, PADS]- not a flatPADSand nota flat 0. This makes even a "add padding only on the head fragment" fix
insufficient in this corner.
Realistic trigger (does not require exotic tiny buffers)
ETHER_ETHERC_RFLR_DEFAULT_VALUEhardcodes the on-wire frame limit to 1518.Pick any buffer size below that, e.g. a pool of
ether_buffer_size = 576(acommon choice to bound per-frame RAM) with
ETHER_ZEROCOPY_DISABLEandETHER_PADDING_2BYTEfor IP-header alignment. A normal 1500-byte frame thensplits across 3 buffers (576 + 576 + 348). On the end fragment FSP
memcpysRFL + 2bytes and over-reads the RX buffer by 2 bytes; the head/middle fragmentsreturn stale-RFL garbage lengths. No jumbo frames or
S ≤ 64- just bufferssmaller than the MTU plus padding, which is a configuration the API otherwise
appears to support.
Splitting is unconditional silicon behavior - RD0.RACT clears "when the receive
buffer becomes full" (p.932), i.e. when the single frame currently being
written into that buffer reaches RBL before it ends (§31.3.3: "if the data
length of the received frame is longer than the buffer length … when the receive
buffer becomes full"). There is no "single buffer / truncate" mode. And
R_ETHER_Readnever inspects RD0.RFP[1:0] (it gates only on RACT, RFS7_RMAF, andRFE - L901-L940), so it returns every consumed fragment, head/middle/end
alike, one per call. With
padding != 0thereceived_size > 0data gate(
ETHER_NO_DATA = 0, L129) is always satisfied, so each buggy fragment is in factsurfaced to the caller - the defect is reached on every fragment of every split
frame, not just occasionally.
Impact
PADS(≤3) bytes perend fragment (non-zerocopy). With a large stale RFL on a head/middle fragment
the over-read can be much larger. No write overflow of the destination iff the
caller sized it
≥ buffer_size + padding; the API does not document thatrequirement, so a caller sizing the destination to
buffer_size(the obviouschoice) gets a 1–
PADS-byte destination overflow.they cannot be reassembled correctly through this API. (Zerocopy avoids the
memcpy/over-read but the length is still wrong, leading to over-reads down the line.)Notes / open question for Renesas
The RPADIR section does not specify the multi-buffer interaction. I am unsure about the silicon behavior when the padding insertion point lands at/after a buffer boundary (straddle vs. entirely-next-buffer above). A whole-frame vs. per-fragment definition of RFL on the
01bend descriptor (the field def says "stored in the buffer" = per-fragment; §31.3.3 says "the receive frame length") would also be worth clarifying in the manual.References
ether_instance_descriptor_t(size= RFL,buffer_size= RBL):https://github.com/renesas/fsp/blob/v5.3.0/ra/fsp/inc/instances/r_ether.h
ether_padding_t/padding/padding_offset:https://github.com/renesas/fsp/blob/v5.3.0/ra/fsp/inc/api/r_ether_api.h
R01UH0886EJ0110Rev.1.10: RPADIR p.924; RD0/RD1 fieldspp.931–932; §31.3.3 Reception p.934.