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).
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 fieldssrc/common/pf_eth.c:197: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 walkseth_type_posforward with no bound. This is the path that handsframe_pos/p_bufto the registered frame handler for any frame whoseframe_idmatches an active AR.2.
pf_cpm_c_data_ind()—uint16_tunderflow leads to out-of-bounds readsrc/common/pf_cpm_driver_sw.c,PF_CPM_STATE_RUN/FRUNbranch:p_ind_buf == (uint8_t*)p_buf->payload. If the received frame'sp_buf->len < 4,pos(auint16_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 (ethertype0x8892) it dispatches byframe_idto whichever handler is registered viapf_eth_frame_id_map_add()—pf_cpm_c_data_indis registered for an active AR's cyclicframe_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 ethertype0x8892and an arbitraryframe_id. Cyclic-dataframe_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.cinlines the realpf_cpm_driver_sw.c(exposing the staticpf_cpm_c_data_ind), sets apf_iocr_t/pf_cpm_ttoPF_CPM_STATE_RUN, and calls the handler directly with a craftedpnal_buf_t(heappayload,lenin{3, 8}):The two runs differ only in
p_buf->lenstraddling 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: checkp_buf->len >= 4(andp_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).