Title:
Out-of-bounds read in OpENer CreateCommonPacketFormatStructure() via crafted CPF item_count

Advisory Type:
Draft vulnerability advisory

Reporter:
Stelle Inv / @stelleinv / stelleinv@gmail.com

Discovery Date:
2026-03-25

Vendor / Project:
EIPStackGroup / OpENer

Repository:
https://github.com/EIPStackGroup/OpENer.git

Affected Product:
OpENer

Tested Revision:
Commit: 1e99582227c86cbdc7c86caeb7ba04c56134d174
Short commit: 1e99582
Git describe: v2.3-558-g1e99582

Branch:
Detached HEAD / not on a named local branch at time of testing

Vulnerability Class:
CWE-125: Out-of-bounds Read

Affected Component:
CPF parser in CreateCommonPacketFormatStructure()
File: source/src/enet_encap/cpf.c

Relevant Code Region:
Approximately lines 243-301 in source/src/enet_encap/cpf.c

Summary:
An out-of-bounds read exists in OpENer while parsing crafted Common Packet
Format (CPF) data. The vulnerable function,
CreateCommonPacketFormatStructure(), reads an attacker-controlled CPF
item_count value and continues parsing structured items without consistently
enforcing the provided data_length boundary. A malformed packet can cause the
parser to continue reading after the end of the CPF slice, resulting in an
out-of-bounds read.

Technical Details:
The issue was reproduced against commit
1e99582227c86cbdc7c86caeb7ba04c56134d174 (git describe:
v2.3-558-g1e99582).

During debugging, the CPF parser was entered with:
- cpf_start = 0x50e92e
- data_length = 16
- cpf_end = 0x50e93e

The first 16 bytes of the CPF slice were:

  01 e8 00 00 00 00 b2 00
  06 00 04 02 20 01 24 01

The first two bytes, 0x01 0xe8, were parsed as:

  item_count = 0xe801

In CreateCommonPacketFormatStructure(), item_count is read from the message:

  CipUint item_count = GetUintFromMessage(&data);

The function then uses this attacker-controlled value to decide how many items
to parse. Although there is a boundary check for data_item.length, there is no
equivalent strict bounds validation before parsing subsequent address_info_item
entries.

A GDB read watchpoint placed on *cpf_end showed that execution reached:

  source/src/enet_encap/cpf.c:280

with:
- data = cpf_end
- data_length = 16
- item_count = 0xe801

At that point, the parser still attempted to read another field via
GetIntFromMessage(&data), demonstrating that parsing continued after the end of
the 16-byte CPF slice.

Relevant vulnerable logic includes:

  CipUint item_count = GetUintFromMessage(&data);
  ...
  for (size_t j = 0; j < (address_item_count > 2 ? 2 : address_item_count); j++) {
      common_packet_format_data->address_info_item[j].type_id =
          GetIntFromMessage(&data);
      ...
  }

The source also contains an inline comment indicating missing validation:

  /* TODO there needs to be a limit check here??? */

Root Cause:
CreateCommonPacketFormatStructure() trusts the attacker-controlled CPF
item_count and does not consistently validate subsequent structured reads
against the remaining data_length. This allows the parser to continue
processing fields after data has already reached cpf_start + data_length.

Impact:
Confirmed impact: out-of-bounds read (heap-buffer-overflow under ASan).
This can lead to process instability and denial of service. At present, this
report only claims an out-of-bounds read / crash-class impact and does not
claim code execution.

Attack Vector:
Remote network input to the ENIP / CPF parser.

Reproduction:
1. Build a debug or ASan-enabled version of OpENer.
2. Feed the attached minimized PoC input to the target.
3. Under GDB, break on CreateCommonPacketFormatStructure().
4. Record:
   - data
   - data_length
   - cpf_start
   - cpf_end
   - item_count
5. Set a read watchpoint on *cpf_end.
6. Continue execution.
7. Observe that parsing continues at approximately cpf.c:280 even when
   data == cpf_end.

Artifacts:
- poc.bin
- poc.bin.sha256
- environment.txt
- asan_log.txt
- gdb_repro.txt
- gdb_repro_full.txt
- gdb_transcript.txt
- gdb_key_evidence.txt
- advisory_draft.txt
- README.md

Suggested Fix:
1. Validate item_count against the remaining CPF slice length before using it.
2. Before every GetUintFromMessage(), GetIntFromMessage(), and
   GetUdintFromMessage() call, verify that enough bytes remain in the CPF slice.
3. Add explicit bounds checks in the address_info_item parsing loop.
4. Abort parsing and return an error as soon as the parser would advance past
   cpf_start + data_length.
5. Add a regression test using the attached minimized PoC.

Suggested Severity:
Medium
Reasoning: the issue is a confirmed out-of-bounds read reachable via crafted
input and can cause denial of service, but no stronger impact is currently
claimed.

Disclosure Status:
Private report / draft advisory

Vendor Coordination:
Not yet contacted.

Credits:
Discovered and reported by Stelle Inv (@stelleinv)

Appendix: Key Debug Evidence
- Repository: https://github.com/EIPStackGroup/OpENer.git
- Tested commit: 1e99582227c86cbdc7c86caeb7ba04c56134d174
- Git describe: v2.3-558-g1e99582
- CPF slice start: 0x50e92e
- CPF slice length: 16
- CPF slice end: 0x50e93e
- Parsed item_count: 0xe801
- First out-of-bounds read observed at:
  source/src/enet_encap/cpf.c:280
