[BUG]: Resource exhaustion DoS in tcp_server_endpoint_impl::connection::receive()
- 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
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