Automated Threat Modeling

LMDeploy CVE-2026-119706: Unauthenticated RCE via Pickle Deserialization in /update_weights — Technical Breakdown with PoC and Patch Analysis

A summary of CVE-2026-119706, a critical unauthenticated remote code execution flaw in InternLM LMDeploy caused by unsafe pickle deserialization in the /update_weights endpoint. Covers PoC details, proposed patch analysis, and detection guidance.

CVE Analysis

12 min read

Gaurav Sarraf
Gaurav Sarraf

2026-11-02

LMDeploy CVE-2026-119706: Unauthenticated RCE via Pickle Deserialization in /update_weights — Technical Breakdown with PoC and Patch Analysis
Experimental AI-Generated Content

This CVE analysis is an experimental publication that is completely AI-generated. The content may contain errors or inaccuracies and is subject to change as more information becomes available. We are continuously refining our process.

If you have feedback, questions, or notice any errors, please reach out to us.

[email protected]

Introduction

An unauthenticated remote code execution vulnerability in InternLM's LMDeploy toolkit lets anyone who can reach the server's /update_weights endpoint run arbitrary operating system commands by sending a crafted pickle payload. The default configuration binds to 0.0.0.0 and ships without API key authentication, so any internet-exposed LMDeploy instance running versions 0.9.0 through 0.18.0 is trivially exploitable.

LMDeploy is an open source toolkit for compressing, deploying, and serving large language models, developed by the MMRazor and MMDeploy teams under the InternLM project. It supports inference across model families including InternLM, Llama, Qwen, and DeepSeek, and provides a TurboMind backend (optimized for throughput) and a PyTorch backend (optimized for accessibility). As organizations deploy more LLM inference servers in production, the security of these serving frameworks becomes a direct concern for enterprise AI infrastructure.

Technical Information

Root Cause: Unsafe Pickle Deserialization (CWE-502)

The core issue is textbook CWE-502: the server deserializes attacker-controlled data with Python's pickle module and applies no validation or restriction. Python's pickle protocol is unsafe for untrusted input because it supports __reduce__ callbacks that can invoke arbitrary functions during object reconstruction.

The POST /update_weights endpoint accepts a JSON body containing a serialized_named_tensors field. This field accepts a string, a list of strings, or a dictionary. When a string is provided, the backend treats it as base64-encoded serialized data and processes it through the following call chain:

POST /update_weights
  -> UpdateParamsRequest.serialized_named_tensors
  -> api_server.update_params(...)
  -> VariableInterface.async_engine.engine.update_params(request)
  -> pybase64.b64decode(request.serialized_named_tensors)
  -> ForkingPickler.loads(...)
  -> native _pickle.loads(...)   # attacker-controlled __reduce__ callbacks execute here

The ForkingPickler.loads call resolves to CPython's native _pickle.loads. At this point, any __reduce__ callback embedded in the pickle stream executes with the privileges of the api_server process. The base64 encoding step is only a transport representation and provides no security boundary.

Both Backends Are Affected

BackendLocationSecurity Consequence
TurboMindlmdeploy/turbomind/turbomind.py, TurboMind.update_paramsDeserializes request-controlled bytes using ForkingPickler.loads
PyTorchlmdeploy/pytorch/engine/model_agent/agent.py, ModelAgent.update_paramsReaches the same unsafe deserialization operation
HTTP management layerPOST /update_weights via UpdateParamsRequest.serialized_named_tensorsConnects the network request to the backend operation

Switching inference backends does not remove the exposure. Both paths converge on the same unsafe deserialization call.

Why Late Validation Does Not Help

The ordering of operations is the key detail. The pickle deserialization, and therefore any embedded code execution, happens before any tensor shape, structure, or weight validation. A later TypeError or validation failure does not undo the code that already ran during deserialization. The proof of concept below shows this directly.

Default Configuration Maximizes Exposure

The api_server defaults to binding on 0.0.0.0, and api_keys defaults to None. Authentication middleware is installed only when nonempty keys are explicitly configured. So a default deployment exposes the vulnerable management endpoint to the entire network with no authentication gate.

The VulnCheck CVSS 4.0 vector is CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N, scoring 9.3. Under CVSS 3.1, the score is 9.8. Both classify the vulnerability as critical.

Proof of Concept

A public proof of concept is available in GitHub issue #4698, filed against the InternLM/lmdeploy repository. The PoC (repro_update_weights_deser.py) is a local harness that stubs heavyweight CUDA and model dependencies, imports the real TurboMind implementation, and invokes TurboMind.update_params with a crafted pickle payload.

The payload uses a simple Marker class with a __reduce__ method that writes a file to disk:

class Marker: def __reduce__(self): return (out.write_text, ("target update_params deserialized payload\n",))

The harness creates a base64-encoded pickle of this object, constructs a mock request with it in serialized_named_tensors, and calls the real update_params method:

payload = base64.b64encode(pickle.dumps(Marker())).decode() request = types.SimpleNamespace(serialized_named_tensors=payload, finished=False) self_obj = types.SimpleNamespace(devices=[0]) print("loads_module", getattr(pickle.loads, "__module__", None)) print("marker_before", out.exists()) try: target.TurboMind.update_params(self_obj, request) except Exception as exc: print("target_exception_after_loads", type(exc).__name__, str(exc)) print("marker_after", out.exists()) if out.exists(): print("marker_text", out.read_text().strip())

The output confirms that the __reduce__ callback fires during ForkingPickler.loads() before any later validation:

loads_module _pickle marker_before False target_exception_after_loads TypeError 'int' object is not iterable marker_after True marker_text target update_params deserialized payload

The marker file is created before the TypeError, which is an expected failure because the harness uses a marker object rather than a real weight iterator. This demonstrates that arbitrary code execution occurs before tensor validation.

An end-to-end HTTP exploit against a live lmdeploy serve api_server instance would send a POST /update_weights request with the malicious base64-encoded pickle in serialized_named_tensors, reaching the same _pickle.loads sink without authentication.

The full PoC script is available in issue #4698.

Patch Information

A patch has been proposed via PR #4964. As of November 3, 2026, this PR remains open and has not been merged, and no patched release has been published. The PR is labeled Bug:P1 and modifies 9 files across 622 additions and 44 deletions.

What the Patch Does

The core fix inserts a guard at the top of the HTTP endpoint handler in lmdeploy/serve/openai/endpoints/management.py:

if (is_pickle_serialized_named_tensors(request.serialized_named_tensors, request.load_format) and not allow_pickle_update_params()): return create_error_response(HTTPStatus.BAD_REQUEST, _PICKLE_HTTP_ERROR)

This rejects any incoming request whose serialized_named_tensors is a base64 string (the wire format serialize_state_dict() uses for pickle blobs) with HTTP 400 before it ever reaches pickle.loads.

Several new utility functions are introduced in lmdeploy/utils.py:

  • is_pickle_serialized_named_tensors(payload, load_format) detects pickle blobs by checking whether the payload is a str or list[str]. If load_format='safetensors', it returns False to prevent false positives.
  • allow_pickle_update_params() reads the environment variable LMDEPLOY_ALLOW_PICKLE_UPDATE_PARAMS and returns True only when it is explicitly set to '1', acting as a deliberate opt-in for trusted same-node XTuner CUDA IPC scenarios.
  • load_pickled_serialized_named_tensors(serialized_data) is the only codepath that calls ForkingPickler.loads. It checks allow_pickle_update_params() first and raises a ValueError if the opt-in is not set, providing defense in depth.

Safe Serialization Alternatives

The patch introduces two safe alternatives:

  1. Safetensors path: serialize_named_tensors_safetensors() and load_safetensors_serialized_named_tensors() use the safetensors library, a memory-safe binary format that cannot execute arbitrary code during deserialization. Clients set load_format='safetensors' on the request.
  2. Structured dict path: coerce_update_params_tensor(value) accepts JSON-safe tensor specs ({dtype, shape, data}) and explicitly rejects callables and pickle reduce tuples.

Both the TurboMind and PyTorch backends have their _deserialize_weights methods rewritten. The direct from multiprocessing.reduction import ForkingPickler and import pybase64 imports are removed from both backends.

Breaking Change

This is a breaking change. Existing clients that send pickle-encoded weights via serialize_state_dict() to /update_weights will receive HTTP 400 unless the server operator explicitly sets LMDEPLOY_ALLOW_PICKLE_UPDATE_PARAMS=1. The recommended migration is to switch to serialize_named_tensors_safetensors() with load_format='safetensors', or to use alternative endpoints like /update_weights_from_distributed (NCCL) or /update_weights_from_ipc (checkpoint engine).

Compatibility Opt-In Restores Risk

Enabling LMDEPLOY_ALLOW_PICKLE_UPDATE_PARAMS=1 deliberately restores pickle support. The LMDEPLOY_ALLOW_PICKLE_UPDATE_PARAMS variable is registered in lmdeploy/pytorch/envs.py so that Ray's get_all_envs() automatically copies it to worker processes. This is not a harmless compatibility switch. It is an explicit restoration of trusted pickle functionality.

Detection Methods

As of this writing, no formal detection signatures (Sigma rules, YARA rules, Snort/Suricata IDS rules, or Nuclei templates) have been published for CVE-2026-119706. OffSeq's Threat Radar tracker confirms that no public Nuclei template or exploit code has been indexed. Detection at this stage is behavioral and asset-inventory driven.

Network Traffic Monitoring

The primary detection signal is an HTTP POST request to the /update_weights endpoint on an LMDeploy api_server instance. TheHackerWire's advisory recommends that SOC analysts monitor for HTTP POST requests to this endpoint and flag those containing unusually large or malformed base64-encoded strings in the serialized_named_tensors parameter. Legitimate weight update payloads have a characteristic size and structure; crafted pickle objects built for code execution (embedding __reduce__ callbacks) tend to show different entropy and size profiles. Analysts should examine both the length and the Shannon entropy of the base64-encoded values.

Host-Based Process Monitoring

Successful exploitation results in arbitrary command execution under the api_server process. Configure process-tree auditing (Linux auditd or EDR agent telemetry) on hosts running LMDeploy and alert when the Python process hosting api_server forks a shell (sh, bash, cmd.exe) or runs command-line tools that have no reason to appear in an inference pipeline.

Access Log Review

IONIX's advisory urges organizations to review exposed LMDeploy servers for unexpected requests to /update_weights and for signs of compromise. TheHackerWire recommends reviewing access logs for requests to this endpoint that originate from suspicious IP ranges or that arrive at high frequency. Any external or unexpected request to this management endpoint on a production inference server warrants investigation.

Attack Surface Discovery

TheHackerWire suggests the Shodan dork http.title:"LMDeploy API Server" "/update_weights" to locate potentially vulnerable internet-facing servers. This is a useful proactive reconnaissance step for teams managing AI/ML infrastructure.

Version-Based Detection

All versions of InternLM LMDeploy from 0.9.0 through 0.18.0 (inclusive) are affected. The PyPI package name is lmdeploy. Asset inventory tools and software composition analysis (SCA) scanners should flag any installed package matching pkg:pypi/lmdeploy within the affected version range.

Affected Systems and Versions

  • Software: InternLM LMDeploy
  • Affected versions: 0.9.0 through 0.18.0 (inclusive)
  • Package: pkg:pypi/lmdeploy
  • Affected backends: Both TurboMind and PyTorch inference backends
  • Vulnerable endpoint: POST /update_weights
  • Vulnerable configuration: The default configuration (binds to 0.0.0.0, no API keys configured) maximizes exposure, but any reachable instance running an affected version is vulnerable regardless of backend choice
  • Fixed version: None confirmed. PR #4964 remains open and unmerged as of November 3, 2026.

Vendor Security History

LMDeploy has a documented pattern of unsafe deserialization vulnerabilities across several attack surfaces:

CVEMechanismAffected VersionsPatched Version
CVE-2026-119706HTTP weight update pickle deserialization0.9.0 through 0.18.0No fixed release confirmed
CVE-2025-59953Unsafe pickle deserialization in the ZMQ RPC serverAt least 0.9.1 and below 0.10.20.10.2
CVE-2025-66455DistServe P2P connection causes deserialization from an attacker-controlled ZeroMQ peerAt least 0.9.2 and below 0.16.00.16.0
CVE-2025-67729Model checkpoint loading through unsafe torch.load()At or below 0.11None listed
CVE-2026-33626SSRF through vision-language image loadingAt or below 0.12.0None listed

The recurring unsafe deserialization theme is notable, but each vulnerability involves a distinct mechanism, entry point, and remediation. The fixes for CVE-2025-59953 (version 0.10.2) and CVE-2025-66455 (version 0.16.0) do not address CVE-2026-119706. The DistServe P2P fix replaced Python object messaging with JSON messaging on a different endpoint (/distserve/p2p_connect), and the checkpoint vulnerability requires a victim to load a malicious model file, unlike the unauthenticated network request described here.

References

Detect & fix
what others miss

Works with
  • GitHub
  • GitLab
  • Bitbucket
  • Azure DevOps Services
  • Gerrit
  • CVS
  • Jira
  • Linear
  • Slack
  • Security Compass
Explore integrations
Security magnifying glass visualization