Skip to content

R_ETHER_Read adds RPADIR padding to multi-buffer fragments where it does not belong → source over-read + wrong length #445

Description

@Ddystopia

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:

  1. 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.
  2. 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.

  3. 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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions