Skip to content

fix(security): bind balance_serve scheduler ZMQ socket to loopback (#2087)#2091

Merged
yyj6666667 merged 1 commit into
kvcache-ai:mainfrom
Anai-Guo:fix/sched-rpc-loopback-bind
Jul 19, 2026
Merged

fix(security): bind balance_serve scheduler ZMQ socket to loopback (#2087)#2091
yyj6666667 merged 1 commit into
kvcache-ai:mainfrom
Anai-Guo:fix/sched-rpc-loopback-bind

Conversation

@Anai-Guo

Copy link
Copy Markdown
Contributor

Summary

Fixes #2087 (GHSA-83vp-v6wg-x93x): unauthenticated pickle-deserialization RCE on the balance_serve scheduler.

The scheduler binds its ZeroMQ ROUTER socket with tcp://* (i.e. 0.0.0.0, all interfaces) and calls pickle.loads() on every received message with no authentication. Any host that can reach the auto-assigned sched_port can send a crafted pickle whose __reduce__ runs a shell command, achieving remote code execution as the server process.

Fix

Bind the scheduler socket to loopback (tcp://127.0.0.1) instead of tcp://*.

This is safe because the scheduler RPC is purely inter-process on the same host:

  • SchedulerClient.__init__ already connects to tcp://localhost:{sched_port} (sched_rpc.py), and
  • the scheduler is launched as a local subprocess by the balance_serve backend (subprocess.Popen([... sched_rpc.py ...])).

So loopback binding is functionally identical for the legitimate local client, while removing the socket from every non-loopback interface and closing the network attack surface. This implements the primary remediation from the issue ("Bind the scheduler socket to loopback").

Scope

  • Applied to both copies of the file that carry the identical bind:
    • archive/ktransformers/server/balance_serve/sched_rpc.py
    • archive/kt-sft/ktransformers/server/balance_serve/sched_rpc.py
  • Each change is a single line (tcp://*tcp://127.0.0.1).

Left out of scope (larger change): the pickle.loads() on the inter-process channel remains. With the socket now loopback-only the network RCE vector is closed, but replacing pickle with a schema-validated format (JSON/msgpack) or adding ZeroMQ CURVE auth is a broader design change best handled separately — happy to follow up if maintainers want it.

🤖 Generated with Claude Code

…vcache-ai#2087)

The balance_serve scheduler binds its ROUTER socket with tcp://*, i.e.
0.0.0.0 (all interfaces), and calls pickle.loads() on every received
message with no authentication. Any host that can reach the auto-assigned
sched_port can send a crafted pickle whose __reduce__ runs a shell command,
achieving unauthenticated remote code execution as the server process
(GHSA-83vp-v6wg-x93x).

The scheduler RPC is purely inter-process: SchedulerClient always connects
to tcp://localhost:{sched_port}, and the server is launched as a local
subprocess. Binding to tcp://127.0.0.1 is therefore functionally identical
for the legitimate local client while removing the socket from every
non-loopback interface, closing the network attack surface.

Applied to both the main and kt-sft copies of sched_rpc.py.
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Caution

The consumer version of Gemini Code Assist on GitHub has been sunset. All code review activity has officially ceased.

@yyj6666667

yyj6666667 commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator

Good security hardening. Binding the scheduler ZMQ socket to 127.0.0.1 prevents exposing the internal control channel on external interfaces while preserving same-host communication.

@yyj6666667
yyj6666667 merged commit def0f93 into kvcache-ai:main Jul 19, 2026
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Unauthenticated pickle deserialization RCE on the balance_serve scheduler ZMQ socket (bound on all interfaces)

2 participants