mirror of
https://github.com/kvcache-ai/ktransformers.git
synced 2026-11-05 01:07:28 +00:00
[GH-ISSUE #2087] Unauthenticated pickle deserialization RCE on the balance_serve scheduler ZMQ socket (bound on all interfaces) #1269
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
kvcache-ai/ktransformers#1269
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Originally created by @geo-chen on GitHub (Jul 13, 2026).
Original GitHub issue: https://github.com/kvcache-ai/ktransformers/issues/2087
Reminder
System Info
version: Commit
89d30a3reported on 12 June 2026: https://github.com/kvcache-ai/ktransformers/security/advisories/GHSA-83vp-v6wg-x93x
Reproduction
Summary
The ktransformers
balance_servescheduler (the default high-performance backend) binds a ZeroMQ ROUTER socket on all network interfaces with no authentication and callspickle.loadson every received message. Any host that can reach the scheduler port can send a crafted pickle stream whose__reduce__executes a shell command, achieving remote code execution as the server process. Confirmed end to end against the socket: anos.systemgadget executed via thepickle.loadson the wildcard-bound socket.Details
archive/ktransformers/server/balance_serve/sched_rpc.py:There is no authentication, CURVE/TLS, or allowlist before the
pickle.loads. The scheduler is launched as a subprocess in the default backend (archive/ktransformers/server/backend/interfaces/balance_serve.py~line 495,subprocess.Popen([... sched_rpc.py ...])).tcp://*binds0.0.0.0, so the socket is reachable from the network. Thesched_portis auto-assigned from a free local port, so an attacker must discover it (a port scan), but the bind itself is on all interfaces and unauthenticated.PoC
The scheduler binds
tcp://*:{sched_port}(archive/.../balance_serve/sched_rpc.py:43,ssshows0.0.0.0:PORT) andworker_routinedoespickle.loads(message)(:70) with no auth; it is launched as a subprocess by the defaultbalance_servebackend (backend/interfaces/balance_serve.py:495). An unauthenticated client sends a pickle gadget:Validated against the
sched_rpc.py: theSchedulerServer.__init__ran theself.frontend.bind("tcp://*:<port>")(ssconfirmed0.0.0.0:<port>listening), and theworker_routine'spickle.loads(message)executed the gadget (the marker file contained the output ofid). Only import-time CUDA dependencies were stubbed so the module could load; the bind andpickle.loadslines are the genuine, unmodified code. The server's subsequent error ('int' object has no attribute 'get') confirmspickle.loadsreturned the gadget's result before any dispatch.Impact
An attacker who can reach the scheduler port (which is bound on all interfaces by default) executes arbitrary code as the ktransformers server process, with no authentication. On a deployment exposed to a LAN or the internet this is remote code execution; the only barrier is discovering the auto-assigned port.
Remediation
Bind the scheduler socket to loopback (
tcp://127.0.0.1:<port>) rather thantcp://*, and do not deserialize untrusted network input with pickle: replacepickle.loadswith a safe, schema-validated format (JSON, protobuf, msgpack with a restricted type set) for the inter-process RPC, or add ZeroMQ CURVE authentication and an allowlist. Inter-process scheduler RPC should not be exposed on the network at all.Others
No response