From 48b628c358d7447056dedc6f8fb34b11915a8e18 Mon Sep 17 00:00:00 2001 From: sysy <2772196789@qq.com> Date: Mon, 15 Jun 2026 21:20:05 -0400 Subject: [PATCH] fix(balance_serve): bind scheduler RPC to loopback to close pre-auth pickle RCE The balance_serve scheduler RPC (sched_rpc.py) binds its ZMQ ROUTER socket to tcp://*:{sched_port} and deserializes every received frame with pickle.loads. With no authentication, allowlist, or format validation, any peer that can reach the port can execute arbitrary code under the server process identity by sending a crafted pickle payload (GitHub issue #2042, Finding 1). The scheduler RPC is local-only by design: it transports CUDA IPC tensor handles produced by mp.reductions.reduce_tensor (valid only on the same host), and SchedulerClient always connects to localhost. Binding to the loopback interface therefore removes the network attack surface without changing the wire protocol, eliminating the pre-auth remote RCE. --- archive/ktransformers/server/balance_serve/sched_rpc.py | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/archive/ktransformers/server/balance_serve/sched_rpc.py b/archive/ktransformers/server/balance_serve/sched_rpc.py index ab8b0dd7d..bb2a6ffac 100644 --- a/archive/ktransformers/server/balance_serve/sched_rpc.py +++ b/archive/ktransformers/server/balance_serve/sched_rpc.py @@ -40,7 +40,14 @@ def __init__(self, settings, main_args): self.context = zmq.Context() self.frontend = self.context.socket(zmq.ROUTER) print(f"sched zmq rpc server on port {main_args.sched_port}") - self.frontend.bind(f"tcp://*:{main_args.sched_port}") + # Security: the scheduler RPC speaks pickle in both directions and has no + # authentication, so any peer that can reach this socket can achieve RCE + # via a crafted pickle payload. The protocol carries CUDA IPC tensor + # handles (mp.reductions.reduce_tensor) that are only valid on the same + # host, and SchedulerClient always connects to localhost, so this RPC is + # local-only by design. Bind to the loopback interface instead of all + # interfaces (tcp://*) so the pickle sink is never exposed to the network. + self.frontend.bind(f"tcp://127.0.0.1:{main_args.sched_port}") # 创建内部的 DEALER 套接字,用于与工作线程通信 self.backend = self.context.socket(zmq.DEALER)