vllm p2p never connected zmq_exhaustion
Summary
When the OffloadingConnector uses TieringOffloadingSpec with a P2P secondary tier, an API client can submit many unique non-listening peer addresses through kv_transfer_params.prefill. vLLM keeps every never-established P2P session and its ZeroMQ sockets after the request finishes. On vLLM 0.26.0, 340 such peers filled the default ZeroMQ context socket quota; creating the next peer raised an uncaught ZMQError, terminated EngineCore, and shut down the API server. The path was reproduced with a normal API key and no access to the internal P2P listener.
Details
Root cause
In vLLM 0.26.0, P2PSecondaryTierManager.on_new_request() reads the following request-controlled fields:
```json
{
"kv_transfer_params": {
"prefill": {
"kv_request_id": "id",
"remote_host": "127.0.0.1",
"remote_port": 49101
}
}
}
`
on_new_request() calls _get_or_create_session(peer_id). This calls ZmqTransport.connect(), creates a P2PSession, and stores it in _sessions before the asynchronous ZeroMQ connection has been established.
ZmqTransport._open_connection() creates a DEALER, enables a libzmq monitor, creates a PAIR socket for that monitor, calls dealer.connect(), and immediately stores the resulting ZmqConnection in _connections. Returning from dealer.connect() does not prove that the peer is reachable.
The monitor...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| vllm PyPI | < 0.32.0 | 0.32.0 |
Details and references
### Summary When the `OffloadingConnector` uses `TieringOffloadingSpec` with a P2P secondary tier, an API client can submit many unique non-listening peer addresses through `kv_transfer_params.prefill`. vLLM keeps every never-established P2P session and its ZeroMQ sockets after the request finishes. On vLLM 0.26.0, 340 such peers filled the default ZeroMQ context socket quota; creating the next peer raised an uncaught `ZMQError`, terminated `EngineCore`, and shut down the API server. The path was reproduced with a normal API key and no access to the internal P2P listener. ### Details #### Root cause In vLLM 0.26.0, `P2PSecondaryTierManager.on_new_request()` reads the following request-controlled fields: ```json { "kv_transfer_params": { "prefill": { "kv_request_id": "id", "remote_host": "127.0.0.1", "remote_port": 49101 } } } ``` `on_new_request()` calls `_get_or_create_session(peer_id)`. This calls `ZmqTransport.connect()`, creates a `P2PSession`, and stores it in `_sessions` before the asynchronous ZeroMQ connection has been established. `ZmqTransport._open_connection()` creates a DEALER, enables a libzmq monitor, creates a PAIR socket for that monitor, calls `dealer.connect()`, and immediately stores the resulting `ZmqConnection` in `_connections`. Returning from `dealer.connect()` does not prove that the peer is reachable. The monitor subscribes only to `zmq.EVENT_DISCONNECTED`. A peer that never accepts the initial connection produced `EVENT_CONNECT_DELAYED`, `EVENT_CONNECT_RETRIED`, and `EVENT_CLOSED`, but no `EVENT_DISCONNECTED` during a 60-second observation. The production monitor therefore never calls `mark_dead()`, and `ZmqConnection.alive` remains true. Request completion calls `session.finish_request()`, but does not close or remove the peer session. The manager reaps only a session for which `session.connected and not session.alive` is true. A never-established peer remains `connected=True`, `alive=True`, and `ready=False`. There is no establishment deadline, idle eviction, or limit on pending or total peers. Each unique retained peer uses about three sockets in the same ZeroMQ context: the DEALER, the monitor endpoint created by libzmq, and the PAIR monitor client. With the measured default `MAX_SOCKETS=1023`, 340 peers use approximately 1,020 sockets in addition to the local ROUTER. The next peer fails while creating its monitor PAIR. The exception escapes through `on_new_request()` and the scheduler into the EngineCore loop: ```text P2PSecondaryTierManager._get_or_create_session() -> ZmqTransport.connect() -> ZmqTransport._open_connection() -> monitor_sock = self._zmq_ctx.socket(zmq.PAIR) -> zmq.error.ZMQError: Too many open files -> EngineDeadError ``` The process open-files limit was 65,536. The EngineCore had about 1,215 file descriptors at the failure point, so the failure was not the operating-system FD limit. #### Boundary and controls Each row below used a fresh vLLM process and pre-generated unique non-listening loopback ports. | Test | Result | |---|---| | Default `MAX_SOCKETS=1023`, 340 unique peers | 340 HTTP 200 responses; 340 active peers; service healthy | | Default limit, 344 unique peers, concurrency 4 | 340 HTTP 200 and 4 HTTP 500 responses; EngineCore and API exited | | Default limit, 344 unique peers, concurrency 1 | Same 340-peer ceiling and service exit | | Same endpoint repeated 512 times | 512 HTTP 200 responses; one active peer; service healthy | | 512 requests without P2P parameters | 512 HTTP 200 responses; no active peers; service healthy | A diagnostic A/B changed only the context `MAX_SOCKETS` value. Raising it from 1,023 to 2,048 moved the last fully successful point from 340 to 682 peers. The 683rd peer then raised the same `ZMQError` and terminated the service. The approximately 2.01x movement in the boundary identifies the ZeroMQ context quota as the exhausted resource. The failure runs used logging-only instrumentation a
- CVSS 3.1
- CVSS:3.1/AV:N/AC:L/PR:L/UI:N/S:U/C:N/I:N/A:H
- Severity from
- GitHub (reviewed advisory)
- Weakness
- CWE-770
- Also known as
- CVE-2026-94624, PYSEC-2026-4006
- github.com/vllm-project/vllm/security/advisories/GHSA-frvg-qw88-hww9
- nvd.nist.gov/vuln/detail/CVE-2026-94624
- github.com/vllm-project/vllm/pull/51504
- github.com/vllm-project/vllm/commit/93d2cab35e8ae66bbaa1dea1c077986d5a79eb80
- github.com/pypa/advisory-database/tree/main/vulns/vllm/PYSEC-2026-4006.yaml
- github.com/vllm-project/vllm
- github.com/vllm-project/vllm/blob/v0.29.0/vllm/v1/kv_offload/tiering/p2p/control/zmq.py#L240-L260
- github.com/vllm-project/vllm/blob/v0.29.0/vllm/v1/kv_offload/tiering/p2p/manager.py#L667-L677
- github.com/vllm-project/vllm/releases/tag/v0.32.0
- www.vulncheck.com/advisories/vllm-through-0.29.0-denial-of-service-via-unbounded-p2p-kv-offloading-sessions