Skip to content

pf_eth_recv/pf_cpm_c_data_ind: unauthenticated OOB read via missing length validation (uint16_t underflow) #602

Description

@router0mail

Two chained bugs on the same network-facing receive path, both confirmed via live ASan reproduction.

1. pf_eth_recv() — no length validation before dereferencing ethertype/VLAN fields

src/common/pf_eth.c:197:

uint16_t eth_type_pos = 2 * sizeof (pnet_ethaddr_t);   /* = 12 */
p_data = (uint16_t *)(&((uint8_t *)p_buf->payload)[eth_type_pos]);
eth_type = ntohs (p_data[0]);                           /* reads payload[12..13] */
while (eth_type == PNAL_ETHTYPE_VLAN)                   /* 0x8100 */
{
   eth_type_pos += 4;                                   /* no iteration bound, no length check */
   p_data = (uint16_t *)(&((uint8_t *)p_buf->payload)[eth_type_pos]);
   eth_type = ntohs (p_data[0]);
}
frame_pos = eth_type_pos + sizeof (uint16_t);

No length parameter, no check of the frame's real received length anywhere in the function. A frame shorter than 14 bytes makes the first read run past received data; a forged run of 0x8100 (VLAN) ethertypes walks eth_type_pos forward with no bound. This is the path that hands frame_pos/p_buf to the registered frame handler for any frame whose frame_id matches an active AR.

2. pf_cpm_c_data_ind()uint16_t underflow leads to out-of-bounds read

src/common/pf_cpm_driver_sw.c, PF_CPM_STATE_RUN/FRUN branch:

uint16_t pos;
...
pos = p_buf->len - 4;            /* cycle counter is at the end of the data */
cycle = (uint16_t)p_ind_buf[pos] * 0x100 + p_ind_buf[pos + 1];
data_status = p_ind_buf[pos + sizeof (uint16_t)];
transfer_status = p_ind_buf[pos + sizeof (uint16_t) + 1];

p_ind_buf == (uint8_t*)p_buf->payload. If the received frame's p_buf->len < 4, pos (a uint16_t) underflows — len=3 -> pos=65535, len=0 -> pos=65532. The four subsequent reads dereference the payload at an offset tens of thousands of bytes past its real size, with no bounds check anywhere on the call path. A second subtraction in the same function (len = p_buf->len - frame_id_pos) has the identical underflow shape.

Reachability

pf_eth_recv() is the raw-Ethernet receive callback. For a PROFINET frame (ethertype 0x8892) it dispatches by frame_id to whichever handler is registered via pf_eth_frame_id_map_add()pf_cpm_c_data_ind is registered for an active AR's cyclic frame_id. PROFINET RT has no L2 authentication: any device on the same Ethernet segment as the controller and this p-net device can craft raw frames with ethertype 0x8892 and an arbitrary frame_id. Cyclic-data frame_ids live in a documented, narrow range (0xC000-0xFBFF), so an on-segment attacker who observes or floods that range reaches this handler with a < 4-byte payload, no controller compromise needed.

Live-fire reproduction (ASan)

repro.c inlines the real pf_cpm_driver_sw.c (exposing the static pf_cpm_c_data_ind), sets a pf_iocr_t/pf_cpm_t to PF_CPM_STATE_RUN, and calls the handler directly with a crafted pnal_buf_t (heap payload, len in {3, 8}):

gcc -fsanitize=address,undefined -fno-omit-frame-pointer -g -O1 \
    -I<stub> -Iinclude -Isrc -Isrc/common -Isrc/device -I<osal>/include \
    repro.c -o repro
EXPLOIT (p_buf->len = 3 -> pos underflows to 65535):
ERROR: AddressSanitizer: SEGV ... The signal is caused by a READ memory access
#0 in pf_cpm_c_data_ind src/common/pf_cpm_driver_sw.c:299

CONTROL (p_buf->len = 8 -> pos = 4, in-bounds): no ASan error,
pf_cpm_c_data_ind returned 1 (handled), exit 0.

The two runs differ only in p_buf->len straddling the 4-byte boundary, isolating the crash to the underflow rather than a harness artifact.

Impact

Unauthenticated (on-segment L2) out-of-bounds read in the PROFINET cyclic-data handler — a wild ~64KB-past-buffer dereference that crashes the process in this reproduction (DoS), with an information-leak/crash-to-control-flow upside depending on what follows the payload in memory.

Suggested fix

pf_eth_recv(): consult the buffer's real received length before any VLAN-tag-loop iteration and before the initial ethertype read; cap the VLAN-tag loop iteration count regardless.

pf_cpm_driver_sw.c: check p_buf->len >= 4 (and p_buf->len >= frame_id_pos) before either subtraction; reject/drop the frame otherwise.

Dup-check

No published security advisories on this repo. Distinct from issue #600 (OOB write in get_check_peer()/pf_block_reader.c, different function/file/direction).

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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