COVESA / COVESA/vsomeip

[BUG]: Resource exhaustion DoS in tcp_server_endpoint_impl::connection::receive()

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

Description

### vSomeip Version

7bcc1e06f16a774e70931ed641f475fbba9c8c64

### Boost Version

1.71

### Environment

Ubuntu 24.04

### Describe the bug

## Summary
A residual bounded resource-exhaustion risk remains in TCP receive path after the mitigation for COVESA/vsomeip #1009.

The original unbounded allocation issue has been mitigated in the latest code by introducing a default maximum message size of 10 MiB and rejecting messages whose claimed SOME/IP size exceeds max_message_size_. However, the TCP endpoint still pre-allocates the full claimed message size up to that cap immediately after receiving only the SOME/IP header.

a client sent only minimal 16-byte SOME/IP headers with Length = 0x00800000 and did not send the corresponding payload. The server allocated an 8,388,616-byte receive buffer per connection.

With 50 concurrent TCP connections, the response-sample process RSS increased from approximately 6.8 MiB to approximately 406 MiB, matching the expected 50 x 8 MiB memory pressure.

This is not a recurrence of the original unbounded #1009 issue. It is a bounded receive-buffer amplification behavior that can still cause practical denial of service in deployments without TCP connection quotas or with constrained ECU memory.

## Vulnerable Code

implementation/endpoints/src/tcp_server_endpoint_impl.cpp:
The TCP receive path reads the attacker-controlled SOME/IP Length field and computes the expected full message size:

```
uint64_t read_message_size =
utility::get_message_size(&recv_buffer_[its_iteration_gap], recv_buffer_size_);

if (read_message_size > max_message_size_) {
VSOMEIP_ERROR_P << instance_name_
<< "Message size exceeds allowed maximum: "
<< read_message_size
<< " local: " << get_address_port_local()
<< " remote: " << get_address_port_remote();
return;
}

uint32_t current_message_size = static_cast(read_message_size);
```

If the full message has not yet arrived, the code stores the missing capacity:

```
} else if (current_message_size > recv_buffer_size_) {
missing_capacity_ =
current_message_size - static_cast(recv_buffer_size_);
}
```

The next receive operation grows the buffer to the full claimed message size:
```
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);

if (recv_buffer_.size() > 1048576) {
VSOMEIP_INFO_P << instance_name_
<< "recv_buffer size is: "
<< recv_buffer_.size()
<< " local: " << get_address_port_local()
<< " remote: " << get_address_port_remote();
}
}
```

The message size is derived from the SOME/IP header:
```
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]);
}
return its_size;
}
```

The Length field at bytes 4..7 is controlled by the remote TCP peer.

## Root Cause

The TCP receive implementation pre-allocates a contiguous receive buffer based on the claimed SOME/IP message size before receiving the corresponding payload.

This behavior appears intentional for TCP stream reassembly and performance:

- it keeps each SOME/IP message in one contiguous buffer,
- avoids repeated reallocations,
- simplifies parser and routing handoff.

However, it creates a memory amplification primitive:

```
Attacker input: 16-byte SOME/IP header
Server allocation: up to configured max_message_size_
```
The latest code mitigates the previous unbounded case by setting:
```
DEFAULT_MAX_MESSAGE_SIZE = 10 * 1024 * 1024; // 10 MiB
```
and by rejecting:
```
read_message_size > max_message_size_
```

Therefore, the original #1009 unbounded allocation is addressed.

The remaining issue is that any size below the configured cap can still cause immediate per-connection allocation before payload arrival. Since no application-level TCP connection count limit or global receive-buffer quota was identified, memory usage can grow linearly with the number of concurrent TCP connections:

```
N connections × allowed message size
```

### Reproduction Steps

_No response_

### Expected behaviour

## Expected Behaviour

The TCP receive path should not allocate the full claimed SOME/IP message size immediately after receiving only the header.

Memory allocation should grow in bounded increments as payload data arrives, rather than immediately expanding to the full claimed SOME/IP Length after only the header is received.

In addition, deployments should be able to enforce resource controls such as:

- per-source TCP connection limits,
- per-endpoint TCP connection limits,
- global receive-buffer memory quotas,
- partial-message timeouts.
-
The existing max-payload-size-reliable and payload-sizes settings limit the maximum message size per connection, but they do not prevent many concurrent connections from consuming:

```
N × max_message_size
```

### Logs and Screenshots

```
Target:
2026-07-09 13:59:19.760346 service-sample [info] Initializing vsomeip (3.7.4) application "service-sample".
2026-07-09 13:59:19.761558 service-sample [info] vSomeIP 3.7.4 (7bcc1e06@master)
2026-07-09 13:59:19.762022 service-sample [info] Application/Client 1277 (service-sample) is registered.

TCP endpoint:
2026-07-09 13:59:19.762333 service-sample [info] tsei::init: #1:: 127.0.0.1:30509
2026-07-09 13:59:19.762413 service-sample [info] tsei::start: #1::127.0.0.1:30509:: Done
2026-07-09 13:59:19.762440 service-sample [info] OFFER(1277): [1234.5678:0.0] (true)

Receive-buffer expansion:
2026-07-09 13:59:22.813657 service-sample [info] tsei::receive: #1::127.0.0.1:30509:: 127.0.0.1:34102::recv_buffer size is: 8388616 local: 127.0.0.1:30509 remote: 127.0.0.1:34102
2026-07-09 13:59:22.814233 service-sample [info] tsei::receive: #1::127.0.0.1:30509:: 127.0.0.1:34098::recv_buffer size is: 8388616 local: 127.0.0.1:30509 remote: 127.0.0.1:34098
2026-07-09 13:59:22.823242 service-sample [info] tsei::receive: #1::127.0.0.1:30509:: 127.0.0.1:34128::recv_buffer size is: 8388616 local: 127.0.0.1:30509 remote: 127.0.0.1:34128

Process memory during test:
PID RSS VSZ COMMAND
58627 416512 796560 response-sample

Process memory after connections closed:
PID RSS VSZ COMMAND
58627 6968 386760 response-sample

Interpretation:
Length=0x00800000 gives an expected SOME/IP message size of 8 + 0x00800000 = 8,388,616 bytes.
The server allocated a recv_buffer of 8,388,616 bytes per TCP connection.
With 50 concurrent TCP connections, RSS increased from 6,968 KiB to 416,512 KiB.
The delta is 409,544 KiB (~400 MiB), matching the expected 50 × 8 MiB memory pressure.
```

Contributor guide

Open the contributing guide

Research direction

Start in implementation/endpoints/src/tcp_server_endpoint_impl.cpp at tcp_server_endpoint_impl::connection::receive(), then trace utility::get_message_size and the max_message_size_ settings. Reproduce the header-only TCP input described in the issue and inspect how concurrent connections affect recv_buffer_. Done means the receive path avoids full claimed-size allocation before payload arrival and supports the requested resource controls.

Written by the indexing model from the issue text.

Assessment

Tech stack
cpp
Domain
networking, security
Issue type
Bug
Difficulty
5/5
Estimated time
Over a week
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
42/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.