rtlabs-com / rtlabs-com/p-net

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

Open
#602 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
C
Stars
599
Forks
234
PR merge metrics
No merged PRs in 30d

Description

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

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start with src/common/pf_eth.c and src/common/pf_cpm_driver_sw.c, focusing on the receive path and the PF_CPM_STATE_RUN/FRUN branch described in the issue. Build and run the ASan reproduction in repro.c to confirm the short-frame failures. Done means malformed short frames are rejected safely, VLAN parsing is bounded, and the reported out-of-bounds reads no longer occur.

Written by the indexing model from the issue text.

Assessment

Tech stack
c
Domain
networking, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
65/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.