pf_eth_recv/pf_cpm_c_data_ind: unauthenticated OOB read via missing length validation (uint16_t underflow)
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
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- 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