From 8fecbb8b88ac89b5a2454902a75a25d039d43316 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georg=20Lipi=C4=8D?= Date: Wed, 24 Jun 2026 13:11:06 +0100 Subject: [PATCH 1/2] Fix out-of-bounds access in EXI bitstream bounds check MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit exi_bitstream_has_overflow only advanced and validated byte_pos when bit_count reached a full byte (EXI_BITSTREAM_MAX_BIT_COUNT). It never checked that the byte about to be accessed was within the stream before exi_bitstream_read_bit and exi_bitstream_write_bit dereference data[byte_pos]. This allowed two out-of-bounds accesses: - Empty input (data_size == 0): the first bit access dereferences data[0] on a zero-length buffer. Every message is decoded by first reading the 8-bit EXI header, so all decoders are reachable with a zero-byte input. - End-of-buffer off-by-one: when bit_count rolls over at the last valid byte, byte_pos is advanced to data_size (one past the end) and the next access touches data[data_size]. Signed-off-by: Georg Lipič --- .../code_templates/c/static_code/exi_bitstream.c.jinja | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/input/code_templates/c/static_code/exi_bitstream.c.jinja b/src/input/code_templates/c/static_code/exi_bitstream.c.jinja index 78250498..ed736e18 100644 --- a/src/input/code_templates/c/static_code/exi_bitstream.c.jinja +++ b/src/input/code_templates/c/static_code/exi_bitstream.c.jinja @@ -25,10 +25,11 @@ static int exi_bitstream_has_overflow(exi_bitstream_t* stream) } {%- endif %} } - else - { - return EXI_ERROR__BITSTREAM_OVERFLOW; - } + } + + if (stream->byte_pos >= stream->data_size) + { + return EXI_ERROR__BITSTREAM_OVERFLOW; } return EXI_ERROR__NO_ERROR; From 1ec0c544adaf6d43411b1182f05a7a00e3709d7b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Georg=20Lipi=C4=8D?= Date: Thu, 25 Jun 2026 12:11:48 +0100 Subject: [PATCH 2/2] Fix undefined behavior in V2GTP header parsing MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When left-shifting uint8_t bytes from stream_data, each operand is implicitly promoted to signed int. Shifting a value into the sign bit (e.g. stream_data[4] << 24) is undefined behavior in C, which can lead to incorrect payload length and payload id values depending on the compiler. Cast the operands to uint16_t / uint32_t before shifting so the operations are performed on unsigned types of the intended width. Signed-off-by: Georg Lipič --- src/input/code_templates/c/static_code/exi_v2gtp.c.jinja | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/input/code_templates/c/static_code/exi_v2gtp.c.jinja b/src/input/code_templates/c/static_code/exi_v2gtp.c.jinja index 8fb4c737..5730b171 100644 --- a/src/input/code_templates/c/static_code/exi_v2gtp.c.jinja +++ b/src/input/code_templates/c/static_code/exi_v2gtp.c.jinja @@ -48,14 +48,15 @@ int V2GTP20_ReadHeader(const uint8_t* stream_data, uint32_t* stream_payload_leng } /* check payload id */ - payload_id = (stream_data[2] << 8) | stream_data[3]; + payload_id = ((uint16_t)stream_data[2] << 8) | stream_data[3]; if (payload_id != v2gtp20_payload_id) { return V2GTP_ERROR__PAYLOAD_ID_DOES_NOT_MATCH; } /* determine payload length */ - *stream_payload_length = (stream_data[4] << 24) | (stream_data[5] << 16) | (stream_data[6] << 8) | stream_data[7]; + *stream_payload_length = ((uint32_t)stream_data[4] << 24) | ((uint32_t)stream_data[5] << 16) | + ((uint32_t)stream_data[6] << 8) | stream_data[7]; return V2GTP_ERROR__NO_ERROR; }