COVESA / COVESA/vsomeip

[BUG]: Heap buffer overflow in tp_message::add_segment()

Open
#1,052 1 comment 0 reactions 0 assignees View on GitHub
3.7.5 bug
Dominant language
C++
Stars
1.4k
Forks
826
PR merge metrics
No merged PRs in 30d

Description

### vSomeip Version

7bcc1e06f16a774e70931ed641f475fbba9c8c64

### Boost Version

1.74

### Environment

Ubuntu 24.04

### Describe the bug

## Summary

A memory corruption vulnerability exists in the SOME/IP-TP reassembly logic of vsomeip.

The issue is caused by an invalid ordering comparator for TP segment ranges in tp_message.hpp. The comparator is used by std::set to order received TP fragments. For nested or overlapping segment ranges, the comparator can report both A < B and B < A as true, violating the strict weak ordering requirement of std::set.

An unauthenticated attacker able to send UDP SOME/IP-TP packets to a configured vsomeip UDP service endpoint can trigger this condition by sending a crafted sequence of TP fragments, for example:

```
Fragment 1: offset = 0, payload_size = 64 -> segment [0, 63]
Fragment 2: offset = 16, payload_size = 16 -> segment [16, 31]
```

## Vulnerable Code
The vulnerable comparator is defined in tp_message.hpp :
```
struct segment_t {
segment_t(std::uint32_t _start, std::uint32_t _end) : start_(_start), end_(_end) { }

bool operator<(const segment_t& _other) const {
return start_ < _other.start_ || ((start_ >= _other.start_) && (end_ < _other.end_));
};

std::uint32_t start_;
std::uint32_t end_;
};

std::set segments_;
```

The vulnerable comparator is used when inserting a received TP segment in tp_message.cpp
```
const length_t its_offset = tp::get_offset(its_tp_header);
const auto emplace_res =
segments_.emplace(segment_t(its_offset, its_offset + its_segment_size - 1));
```
After insertion, the reassembly logic trusts the ordering of segments_ and uses std::next() / std::prev() to decide how to copy payload bytes into the reassembled message buffer:
```
const auto& seg_current = emplace_res.first;
const auto& seg_next = std::next(seg_current);
```

The memory corruption occurs in the following memcpy() path:
```
std::memcpy(&message_[VSOMEIP_FULL_HEADER_SIZE + its_offset],
&data[VSOMEIP_TP_PAYLOAD_POS],
seg_next->start_ - its_offset);
```

The vulnerable path is reachable from UDP input through the following call chain:
```
udp_server_endpoint_impl::on_unicast_received()
udp_server_endpoint_impl::on_message_received_unlocked()
tp_reassembler::process_tp_message()
tp_message::add_segment()
```

## Root Cause

The root cause is an invalid comparison function for TP segment ranges.

The comparator attempts to order segments by start_ and end_, but it uses start_ >= _other.start_ instead of start_ == _other.start_:

```
return start_ < _other.start_
|| ((start_ >= _other.start_) && (end_ < _other.end_));
```

This violates strict weak ordering.

For example:
```
A = [0, 63]
B = [16, 31]
```

The comparator evaluates:
```
A < B:
0 < 16 == true
=> true

B < A:
16 < 0 == false
16 >= 0 == true
31 < 63 == true
=> true
```

Therefore:

```
A < B == true
B < A == true
```

This is invalid for std::set.

The reassembly logic assumes that segments_ is ordered by increasing TP offset. Once that assumption is broken, seg_next may refer to a segment with a smaller offset than seg_current.

In the reproduced case:

```
seg_current = [16, 31]
seg_next = [0, 63]
its_offset = 16
```
The following expression underflows:
```
seg_next->start_ - its_offset
```
Result:
```
0 - 16 = 0xfffffff0 = 4294967280
```

This value is then passed as the length argument to memcpy() :
```
std::memcpy(&message_[VSOMEIP_FULL_HEADER_SIZE + its_offset],
&data[VSOMEIP_TP_PAYLOAD_POS],
0xfffffff0);
```

The existing length checks in tp_message::check_lengths() do not prevent this condition. They validate individual fragment size, maximum message size, TP flag, length consistency, and offset bounds, but they do not reject nested or overlapping segment ranges before insertion into segments_.

### Reproduction Steps

Send two crafted SOME/IP-TP UDP fragments to a vsomeip UDP service endpoint where SOME/IP-TP is enabled for the target service and method.

Use the following fragment layout:
```
Fragment 1:
TP offset: 0
TP payload size: 64 bytes
more-segments: true
segment range: [0, 63]

Fragment 2:
TP offset: 16
TP payload size: 16 bytes
more-segments: true
segment range: [16, 31]
```

When the second fragment is processed, the corrupted segment ordering can cause tp_message::add_segment() to calculate the memcpy() length as:

```
seg_next->start_ - its_offset
= 0 - 16
= 0xfffffff0
```
This results in an out-of-bounds memory access during SOME/IP-TP reassembly.

### Expected behaviour

An attacker who can send UDP SOME/IP-TP packets to a configured vsomeip UDP service endpoint can trigger memory corruption.

- denial of service
- heap-buffer-overflow
- Out-of-bounds read during memcpy
- Potential out-of-bounds write into the reassembly buffer

The attack does not require authentication at the TP reassembly layer. The attacker needs network reachability to a UDP SOME/IP service endpoint where SOME/IP-TP is enabled for the targeted service and method.

A realistic attack scenario is:
- The victim runs a vsomeip application offering a UDP SOME/IP service with SOME/IP-TP enabled.
- The attacker sends a TP fragment with offset 0 and payload size 64.
- The attacker sends a second TP fragment with offset 16 and payload size 16 using the same service, method, client, session, interface version, message type, source IP, and source UDP port.
- The second fragment is nested inside the first fragment.
- The invalid comparator corrupts the segment ordering in std::set.
- tp_message::add_segment() computes a huge memcpy length due to unsigned integer underflow.
- The process crashes or corrupts memory.

### Logs and Screenshots

```
==51702==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x504000000034 at pc 0x75719461942e bp 0x7ffd7a197160 sp 0x7ffd7a196908
READ of size 4294967280 at 0x504000000034 thread T0
#0 0x75719461942d in memcpy ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc:115
#1 0x5a2f2fc39aed in vsomeip_v3::tp::tp_message::add_segment(unsigned char const*, unsigned int) implementation/endpoints/src/tp_message.cpp:140
#2 0x5a2f2fbd4bee in vsomeip_v3::tp::tp_reassembler::process_tp_message(unsigned char const*, unsigned int, boost::asio::ip::address const&, unsigned short) implementation/endpoints/src/tp_reassembler.cpp:59
#3 0x5a2f2fb789a5 in main root/vsomeip/tp_reassembler_inprocess_poc.cpp:156
#4 0x7571939841c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#5 0x75719398428a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#6 0x5a2f2fb77784 in _start (root/vsomeip/tp_reassembler_inprocess_poc+0x24c784) (BuildId: e328aebe482165fa149f612b55ae15b859d86672)

0x504000000034 is located 0 bytes after 36-byte region [0x504000000010,0x504000000034)
allocated by thread T0 here:
#0 0x75719461c548 in operator new(unsigned long) ../../../../src/libsanitizer/asan/asan_new_delete.cpp:95
#1 0x5a2f2fbb7ff8 in std::__new_allocator::allocate(unsigned long, void const*) /usr/include/c++/13/bits/new_allocator.h:151
#2 0x5a2f2fbb5a4b in std::allocator::allocate(unsigned long) /usr/include/c++/13/bits/allocator.h:198
#3 0x5a2f2fbb5a4b in std::allocator_traits >::allocate(std::allocator&, unsigned long) /usr/include/c++/13/bits/alloc_traits.h:482
#4 0x5a2f2fbb5a4b in std::_Vector_base >::_M_allocate(unsigned long) /usr/include/c++/13/bits/stl_vector.h:381
#5 0x5a2f2fbb4643 in std::_Vector_base >::_M_create_storage(unsigned long) (root/vsomeip/tp_reassembler_inprocess_poc+0x289643) (BuildId: e328aebe482165fa149f612b55ae15b859d86672)
#6 0x5a2f2fbb1722 in std::_Vector_base >::_Vector_base(unsigned long, std::allocator const&) (root/vsomeip/tp_reassembler_inprocess_poc+0x286722) (BuildId: e328aebe482165fa149f612b55ae15b859d86672)
#7 0x5a2f2fbab694 in std::vector >::vector(unsigned long, unsigned char const&, std::allocator const&) (root/vsomeip/tp_reassembler_inprocess_poc+0x280694) (BuildId: e328aebe482165fa149f612b55ae15b859d86672)
#8 0x5a2f2fb77d83 in make_tp_packet root/vsomeip/tp_reassembler_inprocess_poc.cpp:48
#9 0x5a2f2fb78567 in main root/vsomeip/tp_reassembler_inprocess_poc.cpp:138
#10 0x7571939841c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#11 0x75719398428a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 8e9fd827446c24067541ac5390e6f527fb5947bb)
#12 0x5a2f2fb77784 in _start (root/vsomeip/tp_reassembler_inprocess_poc+0x24c784) (BuildId: e328aebe482165fa149f612b55ae15b859d86672)

SUMMARY: AddressSanitizer: heap-buffer-overflow ../../../../src/libsanitizer/sanitizer_common/sanitizer_common_interceptors_memintrinsics.inc:115 in memcpy
Shadow bytes around the buggy address:
0x503ffffffd80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x503ffffffe00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x503ffffffe80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x503fffffff00: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
0x503fffffff80: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00
=>0x504000000000: fa fa 00 00 00 00[04]fa fa fa 00 00 00 00 00 fa
0x504000000080: fa fa 00 00 00 00 00 fa fa fa fa fa fa fa fa fa
0x504000000100: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000000180: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000000200: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000000280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==51702==ABORTING

```

Contributor guide

Open the contributing guide

Research direction

Start with tp_message.hpp and implementation/endpoints/src/tp_message.cpp, then trace the UDP path through tp_reassembler.cpp. Run the supplied nested-fragment reproduction under AddressSanitizer and inspect segment ordering and the add_segment() memcpy path. Done means the crafted fragments no longer cause invalid ordering or an ASan heap-buffer-overflow during reassembly.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
networking, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
55/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.