vSomeip Version
v3.6.1
Boost Version
1.71
Environment
ubuntu20.04
Describe the bug
1. Overview
| Field |
Value |
| Affected Software |
vSomeIP 3.6.1 (COVESA/vsomeip) |
| Component |
TCP Server Endpoint (tcp_server_endpoint_impl) |
| CWE |
CWE-789: Memory Allocation with Excessive Size Value |
| CVSS 3.1 Vector |
AV:P/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H |
| CVSS 3.1 Score |
4.6 (MEDIUM) |
| Attack Vector |
Physical — requires access to the in-vehicle Ethernet bus |
| Authentication |
None required |
| Impact |
Denial of Service via memory exhaustion |
2. CVSS Scoring Rationale
| Metric |
Value |
Justification |
| Attack Vector |
Physical (P) |
SOME/IP operates on in-vehicle Ethernet. The attacker must physically connect to the vehicle network (e.g., via OBD-II port or direct Ethernet bus tap). |
| Attack Complexity |
Low (L) |
No race conditions, no special configuration required. Default configuration is vulnerable. |
| Privileges Required |
None (N) |
The TCP endpoint performs no authentication on incoming connections. |
| User Interaction |
None (N) |
No user action required. |
| Scope |
Unchanged (U) |
Impact is confined to the vSomeIP process. |
| Confidentiality |
None (N) |
No information disclosure. |
| Integrity |
None (N) |
No data modification. |
| Availability |
High (H) |
The target process can be forced to consume all available system memory, leading to OOM termination or system-wide resource starvation. |
Note on Attack Vector: If the SOME/IP endpoint is reachable through a Telematics Control Unit (T-Box) or gateway with network bridging, the Attack Vector should be reclassified as Adjacent (A) or Network (N), raising the score to 6.5 or 7.5 respectively.
3. Vulnerability Location
Primary File
implementation/endpoints/src/tcp_server_endpoint_impl.cpp
| Code Path |
Line(s) |
Description |
receive_cbk() |
586 |
get_message_size() reads the Length field from attacker-controlled data |
receive_cbk() |
591 |
Truncates 64-bit size to uint32_t (current_message_size) |
receive_cbk() |
719 |
max_message_size_ check — bypassed because default is MESSAGE_SIZE_UNLIMITED |
receive_cbk() |
740–741 |
Computes missing_capacity_ directly from attacker-controlled current_message_size |
receive() |
449–453 |
Allocates buffer of size recv_buffer_size_ + missing_capacity_ — no upper bound |
Supporting Files
| File |
Line(s) |
Description |
implementation/utility/src/utility.cpp |
48–53 |
get_message_size(): returns VSOMEIP_SOMEIP_HEADER_SIZE (8) + Length_field |
implementation/configuration/include/internal.hpp |
170 |
MESSAGE_SIZE_UNLIMITED = std::numeric_limits<uint32_t>::max() (0xFFFFFFFF) |
implementation/configuration/src/configuration_impl.cpp |
3397 |
get_max_message_size_reliable() returns MESSAGE_SIZE_UNLIMITED when no max is configured |
4. Root Cause Analysis
4.1 Data Flow
The SOME/IP protocol header is 16 bytes:
Byte 0- 1: Service ID
Byte 2- 3: Method ID
Byte 4- 7: Length ← attacker-controlled
Byte 8- 9: Client ID
Byte 10-11: Session ID
Byte 12: Protocol Version
Byte 13: Interface Version
Byte 14: Message Type
Byte 15: Return Code
The Length field (bytes 4–7) specifies the number of bytes following the first 8 bytes of the header. The total message size is therefore 8 + Length.
4.2 Vulnerable Code Path
Step 1 — receive_cbk() reads the attacker-supplied Length field:
// tcp_server_endpoint_impl.cpp, line 586
uint64_t read_message_size =
utility::get_message_size(&recv_buffer_[its_iteration_gap], recv_buffer_size_);
get_message_size() in utility.cpp line 48–53:
uint64_t utility::get_message_size(const byte_t* _data, size_t _size) {
uint64_t its_size(0);
if (VSOMEIP_SOMEIP_HEADER_SIZE <= _size) {
its_size = VSOMEIP_SOMEIP_HEADER_SIZE
+ bithelper::read_uint32_be(&_data[4]); // reads Length field
}
return its_size;
}
For an attacker sending Length = 0x04000000 (64 MB):
read_message_size = 8 + 67,108,864 = 67,108,872
Step 2 — The size is truncated to uint32_t and checked against max_message_size_:
// line 591
uint32_t current_message_size = static_cast<uint32_t>(read_message_size);
// current_message_size = 67,108,872
// line 719 — this check is BYPASSED because max_message_size_ == MESSAGE_SIZE_UNLIMITED
} else if (max_message_size_ != MESSAGE_SIZE_UNLIMITED
&& current_message_size > max_message_size_) {
// ... close connection (never reached with default config)
Since max_message_size_ defaults to MESSAGE_SIZE_UNLIMITED (0xFFFFFFFF), the condition max_message_size_ != MESSAGE_SIZE_UNLIMITED evaluates to false, and the entire size limit check is skipped.
Step 3 — missing_capacity_ is set from attacker-controlled data:
// line 740-741
} else if (current_message_size > recv_buffer_size_) {
missing_capacity_ = current_message_size
- static_cast<std::uint32_t>(recv_buffer_size_);
}
// missing_capacity_ = 67,108,872 - 16 = 67,108,856 (~64 MB)
Step 4 — receive() allocates the buffer unconditionally:
// tcp_server_endpoint_impl.cpp, line 444-453
if (missing_capacity_) {
if (missing_capacity_ > MESSAGE_SIZE_UNLIMITED) { // always false for uint32_t
return;
}
const std::size_t its_required_capacity(recv_buffer_size_ + missing_capacity_);
if (its_capacity < its_required_capacity) {
recv_buffer_.reserve(its_required_capacity);
recv_buffer_.resize(its_required_capacity, 0x0); // ALLOCATES 64 MB
}
}
The check at line 445 (missing_capacity_ > MESSAGE_SIZE_UNLIMITED) is effectively a no-op: missing_capacity_ is uint32_t and MESSAGE_SIZE_UNLIMITED is uint32_t::max(), so this condition can only be true for missing_capacity_ == 0 edge cases. For any valid allocation request, it passes through.
4.3 Why the Existing Size Check Does Not Help
The existing guard at line 719:
if (max_message_size_ != MESSAGE_SIZE_UNLIMITED && current_message_size > max_message_size_)
This check is dead code under default configuration because:
max_message_size_ is initialized from configuration_->get_max_message_size_reliable() (line 102)
- Without explicit configuration, this returns
MESSAGE_SIZE_UNLIMITED (configuration_impl.cpp line 3397)
- The first sub-condition
max_message_size_ != MESSAGE_SIZE_UNLIMITED is false
- Short-circuit evaluation skips the size comparison entirely
4.4 Amplification Factor
| Attack Input |
Server Allocation |
Amplification |
| 16 bytes (one SOME/IP header) |
64 MB |
4,194,304 : 1 |
| 128 bytes (8 connections) |
512 MB |
4,194,304 : 1 |
| 256 bytes (16 connections) |
1,024 MB |
4,194,304 : 1 |
Each 16-byte SOME/IP header with Length = 0x04000000 causes a 64 MB allocation. The amplification ratio is constant regardless of the number of connections.
5. Impact Analysis
5.1 Direct Impact
- Memory Exhaustion: The attacker can force arbitrary memory allocation. With 16 concurrent connections each claiming 256 MB, the server process allocates 4 GB.
- OOM Termination: On systems with limited memory, the Linux OOM killer will terminate the vSomeIP process.
- System-wide Starvation: On embedded automotive ECUs with limited RAM (typically 512 MB–2 GB), even a few malicious connections can starve all other processes.
5.2 Automotive Context
- Service Disruption: If the vSomeIP routing manager is killed by OOM, all SOME/IP services on that ECU become unavailable.
- No Recovery Without Restart: The process must be manually restarted (or by a watchdog). During downtime, safety-critical communication may be interrupted.
- Silent Attack: The allocation happens silently — vSomeIP only logs an info-level message when the buffer exceeds 1 MB (line 454), which is easily missed.
6. Suggested Fix
Option A: Enforce a Hard Maximum Message Size (Recommended)
// In receive_cbk(), before line 740:
constexpr uint32_t VSOMEIP_MAX_TCP_MESSAGE_SIZE_DEFAULT = 64 * 1024; // 64 KB
if (current_message_size > VSOMEIP_MAX_TCP_MESSAGE_SIZE_DEFAULT) {
VSOMEIP_ERROR << instance_name_ << __func__
<< ": message size " << current_message_size
<< " exceeds maximum allowed size. Closing connection.";
its_lock.unlock();
wait_until_sent(boost::asio::error::operation_aborted);
return;
}
Option B: Make the Existing Check Effective
Change line 719 from:
if (max_message_size_ != MESSAGE_SIZE_UNLIMITED && current_message_size > max_message_size_)
to:
if (current_message_size > max_message_size_)
And set a reasonable default for max_message_size_ instead of MESSAGE_SIZE_UNLIMITED.
Option C: Incremental Allocation with Backpressure
Instead of allocating the full claimed size upfront, allocate in chunks and only grow as actual data arrives:
constexpr size_t MAX_RECV_BUFFER_GROW = 64 * 1024; // grow 64 KB at a time
if (missing_capacity_ > MAX_RECV_BUFFER_GROW) {
missing_capacity_ = MAX_RECV_BUFFER_GROW;
}
Reproduction Steps
No response
Expected behaviour
No response
Logs and Screenshots

vSomeip Version
v3.6.1
Boost Version
1.71
Environment
ubuntu20.04
Describe the bug
1. Overview
tcp_server_endpoint_impl)2. CVSS Scoring Rationale
3. Vulnerability Location
Primary File
implementation/endpoints/src/tcp_server_endpoint_impl.cppreceive_cbk()get_message_size()reads the Length field from attacker-controlled datareceive_cbk()uint32_t(current_message_size)receive_cbk()max_message_size_check — bypassed because default isMESSAGE_SIZE_UNLIMITEDreceive_cbk()missing_capacity_directly from attacker-controlledcurrent_message_sizereceive()recv_buffer_size_ + missing_capacity_— no upper boundSupporting Files
implementation/utility/src/utility.cppget_message_size(): returnsVSOMEIP_SOMEIP_HEADER_SIZE (8) + Length_fieldimplementation/configuration/include/internal.hppMESSAGE_SIZE_UNLIMITED = std::numeric_limits<uint32_t>::max()(0xFFFFFFFF)implementation/configuration/src/configuration_impl.cppget_max_message_size_reliable()returnsMESSAGE_SIZE_UNLIMITEDwhen no max is configured4. Root Cause Analysis
4.1 Data Flow
The SOME/IP protocol header is 16 bytes:
The
Lengthfield (bytes 4–7) specifies the number of bytes following the first 8 bytes of the header. The total message size is therefore8 + Length.4.2 Vulnerable Code Path
Step 1 —
receive_cbk()reads the attacker-supplied Length field:get_message_size()inutility.cppline 48–53:For an attacker sending
Length = 0x04000000(64 MB):read_message_size = 8 + 67,108,864 = 67,108,872Step 2 — The size is truncated to
uint32_tand checked againstmax_message_size_:Since
max_message_size_defaults toMESSAGE_SIZE_UNLIMITED(0xFFFFFFFF), the conditionmax_message_size_ != MESSAGE_SIZE_UNLIMITEDevaluates to false, and the entire size limit check is skipped.Step 3 —
missing_capacity_is set from attacker-controlled data:Step 4 —
receive()allocates the buffer unconditionally:The check at line 445 (
missing_capacity_ > MESSAGE_SIZE_UNLIMITED) is effectively a no-op:missing_capacity_isuint32_tandMESSAGE_SIZE_UNLIMITEDisuint32_t::max(), so this condition can only be true formissing_capacity_ == 0edge cases. For any valid allocation request, it passes through.4.3 Why the Existing Size Check Does Not Help
The existing guard at line 719:
if (max_message_size_ != MESSAGE_SIZE_UNLIMITED && current_message_size > max_message_size_)This check is dead code under default configuration because:
max_message_size_is initialized fromconfiguration_->get_max_message_size_reliable()(line 102)MESSAGE_SIZE_UNLIMITED(configuration_impl.cpp line 3397)max_message_size_ != MESSAGE_SIZE_UNLIMITEDisfalse4.4 Amplification Factor
Each 16-byte SOME/IP header with
Length = 0x04000000causes a 64 MB allocation. The amplification ratio is constant regardless of the number of connections.5. Impact Analysis
5.1 Direct Impact
5.2 Automotive Context
6. Suggested Fix
Option A: Enforce a Hard Maximum Message Size (Recommended)
Option B: Make the Existing Check Effective
Change line 719 from:
if (max_message_size_ != MESSAGE_SIZE_UNLIMITED && current_message_size > max_message_size_)to:
if (current_message_size > max_message_size_)And set a reasonable default for
max_message_size_instead ofMESSAGE_SIZE_UNLIMITED.Option C: Incremental Allocation with Backpressure
Instead of allocating the full claimed size upfront, allocate in chunks and only grow as actual data arrives:
Reproduction Steps
No response
Expected behaviour
No response
Logs and Screenshots