Sep 16, 2026·6 min read·2 visits
Unauthenticated remote attackers can execute arbitrary code on LMDeploy hosts prior to v0.10.2 by sending a malicious Python pickle stream to the dynamically opened ZMQ RPC port.
LMDeploy prior to version 0.10.2 is vulnerable to remote code execution because its AsyncRPCServer component implements unauthenticated, remote-accessible communication sockets and uses the insecure pickle.loads() deserializer to process incoming requests.
LMDeploy is an open-source toolkit designed for compressing, deploying, and serving large language models (LLMs). The toolkit implements an asynchronous Remote Procedure Call (RPC) server, AsyncRPCServer, within lmdeploy/pytorch/engine/mp_engine/zmq_rpc.py to manage inter-process or multi-node tensor-parallel and data-parallel inference orchestration.
During parallel execution, nodes exchange serialization states to synchronize inference workloads. The RPC server exposes a ZeroMQ (ZMQ) router socket to handle this inter-node communication. Prior to version 0.10.2, this socket bound to wildcard network interfaces, exposing the system's unauthenticated message handling functionality to the external network.
The exposed RPC handler processes incoming binary payloads without verification or access control. This architectural trust boundary omission allows any network-adjacent actor with TCP access to the dynamically assigned port to send payloads directly to the underlying processing loop. Consequently, the affected versions of LMDeploy suffer from an unauthenticated remote code execution vulnerability, tracked as CVE-2025-59953.
The root cause of CVE-2025-59953 is the insecure use of Python's native pickle module within the message processing routine of AsyncRPCServer. Specifically, the server invokes pickle.loads() directly on received raw byte frames inside the call_and_response() method. The pickle serialization protocol is designed for trusted environments and possesses no intrinsic capability to distinguish safe data structure definitions from arbitrary executable object instructions.
Python's object deserialization architecture executes the instructions encoded in the serialized stream to reconstruct the serialized object. An attacker can construct a serialized byte stream containing instructions that execute arbitrary shell commands or launch external binary processes. The execution occurs when the class reconstruction machinery processes the __reduce__ or __setstate__ methods defined in the malicious payload.
Because the server executed this deserialization routine on unvalidated network packets, any system with TCP routing to the server port is vulnerable. This enables arbitrary code execution with the permissions of the user context running the LMDeploy RPC worker. This behavior is standard for the Python interpreter but introduces a vulnerability when exposed directly to a network socket.
In affected versions of LMDeploy, the initialization of the AsyncRPCServer established a socket address of tcp://*. This configuration binds the ZMQ ROUTER socket to all available network interfaces on the host operating system. The socket is dynamically assigned a port via bind_to_random_port(), which exposes the unauthenticated deserialization entry point to external network traffic.
The patch in commit d86046a0e6b02ecaaf7cdb74346d0477130221c2 modifies the initialization parameters to restrict binding strictly to the local loopback interface. This limits TCP access to local processes or containers sharing the host's loopback interface. Let us look at the code-level change in the initialization routine.
# Vulnerable configuration in lmdeploy/pytorch/engine/mp_engine/zmq_rpc.py
class AsyncRPCServer:
def __init__(self):
address = 'tcp://*'
self.context = zmq.Context()
self.socket = self.context.socket(zmq.ROUTER)
self.port = self.socket.bind_to_random_port(address)# Patched configuration in lmdeploy/pytorch/engine/mp_engine/zmq_rpc.py
class AsyncRPCServer:
def __init__(self):
# Warning: DO NOT allow visit rpc server from external network
# unauthorized access may lead to code execution vulnerability
address = 'tcp://localhost'
self.context = zmq.Context()
self.socket = self.context.socket(zmq.ROUTER)
self.port = self.socket.bind_to_random_port(address)While this patch effectively closes the remote attack vector by restricting interface bindings, the underlying deserialization mechanism remains unchanged. The system still relies on unsafe pickle deserialization, representing a security design trade-off that leaves localized exploit channels intact.
An exploit chain targeting this vulnerability requires the attacker to identify the randomized TCP port allocated by AsyncRPCServer. Since the port is assigned dynamically during the initialization of the model server, attackers scanning external networks will find an open TCP port managed by the ZMQ routing framework. The ZMQ framework responds to initial handshakes, confirming the protocol version and port status.
Once the open port is identified, the attacker crafts a malicious binary payload using the standard pickle format. A typical conceptual exploit creates a custom class containing a __reduce__ method. This method returns a tuple containing a callable, such as os.system or subprocess.Popen, and its corresponding arguments, such as an operating system command or shell execution string.
When the LMDeploy call_and_response function processes the incoming payload via pickle.loads(), the interpreter immediately invokes the callable specified in the reduction payload to recreate the object. This invocation occurs in the security context of the parent LMDeploy process, resulting in complete system compromise if the service runs under privileged credentials.
Although the official patch in version 0.10.2 successfully restricts remote exploitation by shifting the interface binding from 0.0.0.0 to 127.0.0.1, several residual risks must be evaluated. Local privilege escalation (LPE) remains a significant concern. If multiple unprivileged users share access to the same machine or container cluster, any local process can scan the loopback interface, discover the port, and execute code within the context of the running LMDeploy worker.
Server-Side Request Forgery (SSRF) also remains a viable bypass vector. If another application running on the same host exposes an SSRF vulnerability that supports custom TCP socket interactions, an attacker can use that application as a pivot to relay malicious pickle payloads to the local ZMQ server. This bypasses the interface restriction because the connection originates from 127.0.0.1.
Furthermore, in containerized orchestration environments like Kubernetes or Docker, shared network namespaces can allow adjacent containers to access the loopback interface of the target pod or container. This layout circumvents the interface-based isolation patch, making network isolation policies outside of the container namespace necessary to protect the deployment.
To resolve the vulnerability completely, administrators must upgrade LMDeploy to version 0.10.2 or higher. The upgrade ensures that the zmq_rpc.py server limits its socket listener to loopback interfaces, reducing the immediate remote attack surface to zero. Upgrades can be performed using standard Python package management tools like pip.
For environments where immediate upgrading is not possible, host-level firewalling must be implemented. Use configuration tools such as iptables or ufw to drop incoming traffic to non-standard TCP ports from external network zones. Alternatively, restrict the server's network namespace explicitly using container network security policies.
Developers and operators should also implement running the LMDeploy daemon under a dedicated, low-privileged system user rather than as the root user. This step restricts the file system write access and system privilege scope available to any command executed via pickle deserialization.
CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
lmdeploy InternLM | >= 0.9.1, < 0.10.2 | 0.10.2 |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-502 |
| Attack Vector | Network |
| Complexity | Low |
| Privileges Required | None |
| EPSS Score | N/A |
| Exploit Status | Proof-of-Concept |
| CISA KEV Status | Not Listed |
The application deserializes untrusted data without sufficiently verifying that the resulting data will be safe.
A critical access control vulnerability in djust prior to 1.0.7 exposes diagnostic endpoints and remote method-invocation capabilities to unauthorized network actors. The vulnerability arises due to decoupling IP boundary validation into an opt-in middleware that was omitted from official configuration documentation, leaving views to rely solely on the status of Django's DEBUG flag.
The multi-tenant isolation mechanism in djust prior to version 1.0.7 fails open on active WebSocket and Server-Sent Events (SSE) connections. Because the tenant context is stored in thread-local variables and initialized exclusively via HTTP middleware, asynchronous event loops executing ASGI/WebSocket code paths do not carry the resolved tenant identifier. When queries are executed without this context, the default database manager fails open, allowing authenticated users of any tenant to query and read sensitive rows across all other tenant accounts.
CVE-2026-68904 is a high-severity Denial of Service (DoS) vulnerability in the node-opcua library. It arises from a logical flaw in the keepalive session manager combined with incorrect socket termination at the TCP transport layer. When server-side anomalies occur, affected clients fall into an infinite, high-frequency reconnection loop. Due to the use of graceful teardown (socket.end) instead of immediate termination (socket.destroy) during negotiation failures, sockets remain open in the FIN-WAIT-2 state. This accumulates system file descriptors and memory, eventually crashing the client process.
CVE-2026-61593 is a high-severity Cross-Site Request Forgery (CSRF) vulnerability discovered in the Server-Sent Events (SSE) transport layer of djust, an open-source framework that implements Phoenix LiveView-style reactive server-side rendering for Django applications. Before version 1.0.7, a lack of origin verification on the SSE stream endpoint, combined with @csrf_exempt decorators on message POST endpoints, allowed an attacker to hijack active client sessions through cross-origin interactions.
An untrusted search path vulnerability (CWE-426) in the OpenTelemetry.Resources.Host NuGet package on macOS allows a local attacker to execute arbitrary code with elevated privileges by hijacking standard system commands such as sh and ioreg.
CVE-2026-61598 is a high-severity mass-assignment vulnerability (CWE-915) affecting the Python package djust prior to version 1.0.7. An authenticated client can supply arbitrary parameter names to modify public view attributes on the server via WebSocket events, leading to unauthorized state manipulation, authorization bypass, or price tampering.