2026.10.26 · CVE / vsomeip / SOME/IP / memory exhaustion / disclosure · 7 min
CVE-2026-91141: 16 bytes to 512 MiB
A short SOME/IP header led vsomeip 3.7.3 to allocate 512 MiB before receiving the payload.
CVE-2026-91141 is the identifier CERT/CC assigned to my report of memory exhaustion in COVESA vsomeip, published on 26 October 2026 in Vulnerability Note VU#128947. In a 3.7.3 test, I sent a 16-byte SOME/IP header that made a reliable TCP endpoint allocate 512 MiB before receiving the payload. Resident memory rose by 512.3 MiB.
vsomeip implements SOME/IP communication between software services, including services inside vehicles.
The finding
In vsomeip 3.7.3, a peer able to open a TCP connection to a reliable SOME/IP endpoint can make the endpoint allocate memory for a message whose payload has not arrived. The peer sends a syntactically acceptable 16-byte header with a large 32-bit length. With the shipped unlimited reliable-message default, the receive path grows and zero-fills its buffer before receiving the declared message. COVESA’s fix commit 5c669a1 first shipped in 3.7.4 and remains present in 3.7.6: source review shows a 10 MiB default and checks before this allocation. The runtime evidence in this post is from 3.7.3.
I reproduced the behavior on 13 June 2026. A 512 MiB declaration produced about 512.3 MiB of additional resident memory, and vsomeip logged a 536,870,912-byte receive buffer. In a separate run, a 3 GiB declaration under a stated 1.5 GiB limit ended in process termination consistent with memory exhaustion.
The measurements come from my loopback test service on 3.7.3. The client path and the later fix were reviewed in source. Exposure in a deployment depends on whether an attacker can reach a reliable TCP endpoint.
From reproduction to disclosure
I tested a reliable SOME/IP endpoint over TCP. The minimal service only initializes vsomeip and offers service 0x1234, instance 0x5678, on port 30509; it does not need to process a valid application request for the allocation path to run. The peer needs only a TCP connection and bytes that satisfy the early header checks. No SOME/IP credential or completed payload is required at that point.
In tag 3.7.3, VSOMEIP_MAX_TCP_MESSAGE_SIZE is 0, documented as unlimited in interface/vsomeip/defines.hpp. configuration_impl::get_max_message_size_reliable checks an address-and-port override first, then the global reliable setting or that compile-time default. The documented max-payload-size-reliable setting supplies a finite limit, with the per-address-and-port setting taking precedence.
I found the flaw independently while fuzzing vsomeip, reproduced it on 13 June, and CERT/CC received my initial report that day. I sent the full report to COVESA on 16 June and received an automated acknowledgment, but no substantive response to my direct report. The same server-side root cause had already been reported publicly in COVESA issue #1009 on 26 February, and the upstream fix had been authored in May, both before my report. COVESA released the fix in 3.7.4 on 6 July. My report adds the runtime measurements on 3.7.3 below and the source analysis of the client path. I attached my PoC evidence to the CERT case on 15 July. CERT/CC reserved the CVE number on 14 September and published VU#128947 on 26 October. Thanks to Alexander Curtis for coordinating the case.
Where the allocation happens
The decisive source is utility::get_message_size, which reads the length at bytes 4 through 7 once the 8-byte SOME/IP base header is present:
its_size = VSOMEIP_SOMEIP_HEADER_SIZE + bithelper::read_uint32_be(&_data[4]);
The result is stored in uint64_t, but the operands in this tag are 32-bit before assignment. The 512 MiB and 3 GiB sums used here fit, so that type detail does not change either allocation. The later read_message_size > MESSAGE_SIZE_UNLIMITED comparison therefore supplies no finite policy for these values.
In tcp_server_endpoint_impl::connection::receive_cbk, the server updates the byte count, calls get_message_size, and checks whether a complete message is present. A 16-byte header declaring 512 MiB is incomplete. Before requesting more bytes, the code checks protocol version, message type, and return code. The PoC uses 0x01, 0x00, and 0x00, so those checks pass. The finite-size branch at lines 783-800 is skipped when max_message_size_ is MESSAGE_SIZE_UNLIMITED, and lines 802-805 record the missing capacity.
The next call to tcp_server_endpoint_impl::connection::receive turns the declared size into allocation:
recv_buffer_.reserve(its_required_capacity);
recv_buffer_.resize(its_required_capacity, 0x0);
The resize touches the newly allocated bytes before async_receive waits for the payload, which explains the recorded resident-memory growth. The missing_capacity_ guard compares a uint32_t with the all-ones unlimited value and imposes no smaller bound. This is a preallocation failure while handling an incomplete message, before application dispatch. The client endpoint has the corresponding calculation and allocation in tcp_client_endpoint_impl.cpp, but the client path is source-confirmed only in this post.
What an attacker needs
The demonstrated primitive is availability loss through attacker-controlled memory commitment. A peer needs a route to a reliable TCP endpoint and the ability to send bytes. The source and loopback run show that a valid service operation, credential, or complete message is unnecessary for this allocation. Deployment routing and firewall policy determine whether an equivalent peer can reach an endpoint.
The allocation belongs to a connection and is released when that connection closes. On a memory-rich host, sustained pressure therefore depends on holding or repeating connections, while concurrent connections multiply allocations. In the 512 MiB run, memory returned near baseline after close. The 3 GiB run shows termination under a stated memory limit, consistent with exhaustion.
The reproduction
The test used an ASan-enabled Linux build with a loopback service on 127.0.0.1:30509, service 0x1234:0x5678. Service discovery and magic cookies were disabled. For the 512 MiB case, the sender constructs this 16-byte SOME/IP header:
12 34 | 00 01 | 1f ff ff f8 | 00 00 | 00 01 | 01 | 01 | 00 | 00
service method length client session protocol interface type return
The length field is 0x1ffffff8; adding the 8-byte SOME/IP base gives 0x20000000, or 536,870,912 bytes. The inspected sender then performs eight one-byte writes 0.7 seconds apart while holding the connection. This detail matters when describing the stored runtime: it is not a strictly header-only trace. The source-confirmed allocation occurs after the header, before any follow-up byte is needed.
The following excerpts are from the preserved run records, with whitespace normalized:
| Run | Recorded line | Meaning |
|---|---|---|
| 512 MiB | VmRSS: 63868 kB | Baseline RSS |
| 512 MiB | rss_kB=588456 | First post-trigger sample |
| 512 MiB | recv_buffer size is: 536870912 | vsomeip allocation log |
| 512 MiB | rss_kB=64344 | Sample after the connection closed |
| 3 GiB | VmRSS: 61160 kB | Baseline RSS |
| 3 GiB | rss_kB=1034228 | Highest preserved sample before disappearance |
| 3 GiB | follow-up send failed (server closed connection?): [Errno 104] Connection reset by peer | Peer observed reset |
| 3 GiB | Killed | Shell reported process termination |
The 512 MiB RSS delta is 524,588 KiB, about 512.3 MiB. RSS measures the whole process, so the observed change need not match the buffer size exactly. The 3 GiB record uses the 1.5 GiB limit stated by the recording and scripts. It includes no independent kernel or cgroup event counter, so the observed process termination cannot be attributed conclusively to a kernel OOM action. No capped-endpoint or client-side runtime control was recorded. The sender and service are intentionally resource-intensive and belong on a disposable target.
The fix
The reliable receive path needs an effective finite maximum before it grows a buffer. COVESA’s fix commit 5c669a1 introduces DEFAULT_MAX_MESSAGE_SIZE at 10 MiB, makes the reliable configuration getter return it when unset, and checks the declared message and missing capacity against max_message_size_ before reserve and zero-filling resize. The first release containing this fix is 3.7.4, and 3.7.6 retains it. Both were checked in source; the runtime measurements remain those from 3.7.3. Upgrading to a release containing this fix removes the old unlimited default.
Integrators can set max-payload-size-reliable to a finite value suitable for their protocol, and should review any per-address-and-port overrides that take precedence over it. The 10 MiB default introduced in 3.7.4 prevents the 512 MiB and 3 GiB allocations described here on this path. Raising the cap permits allocations up to the raised value, so the setting is a bound for this path rather than immunity to all memory exhaustion. Aggregate incomplete-message memory, per-peer connection counts, and cleanup time are useful additional budgets, but remain proposed hardening in this report.
Regression coverage should send a valid incomplete header above the default, verify bounded memory and connection handling, exercise a per-address-and-port override, and cover both server and client endpoints. Boundary-sized and normal complete messages should remain accepted where configured. The fix commit records a memory-usage test for a malicious large length; keeping that test with incomplete-message and concurrency cases preserves the failure mechanism.
What this shows
The failure happens before application dispatch: the receiver commits memory for bytes the peer has merely promised. The 3.7.4 fix introduces a finite default and enforces it before this growth. An integrator’s override still needs to fit the deployment’s memory budget.
This analysis covers the reproduced 3.7.3 server path and source-reviewed behavior in the client and fixed releases. It does not establish a blanket affected-version range across older branches and every intermediate release.