From 8d4fca59080e9451af87d7af817fe5040796e772 Mon Sep 17 00:00:00 2001 From: Khoa Pham Date: Thu, 26 Mar 2026 23:33:49 -0700 Subject: [PATCH] [Security] 1/N: Bind ZMQ sockets to localhost to prevent unauthenticated remote access (#21435) --- .../runtime/scheduler_client.py | 3 +-- .../srt/disaggregation/encode_receiver.py | 6 ++++-- python/sglang/srt/utils/network.py | 19 ++++++++++--------- 3 files changed, 15 insertions(+), 13 deletions(-) diff --git a/python/sglang/multimodal_gen/runtime/scheduler_client.py b/python/sglang/multimodal_gen/runtime/scheduler_client.py index caec33da4..b009071b7 100644 --- a/python/sglang/multimodal_gen/runtime/scheduler_client.py +++ b/python/sglang/multimodal_gen/runtime/scheduler_client.py @@ -16,9 +16,8 @@ async def run_zeromq_broker(server_args: ServerArgs): It listens for TCP requests from offline clients (e.g., DiffGenerator). """ ctx = zmq.asyncio.Context() - # This is the REP socket that listens for requests from DiffGenerator socket = ctx.socket(zmq.REP) - broker_endpoint = f"tcp://*:{server_args.broker_port}" + broker_endpoint = f"tcp://127.0.0.1:{server_args.broker_port}" socket.bind(broker_endpoint) logger.info(f"ZMQ Broker is listening for offline jobs on {broker_endpoint}") diff --git a/python/sglang/srt/disaggregation/encode_receiver.py b/python/sglang/srt/disaggregation/encode_receiver.py index fa1ea0d4b..6b70e15ef 100644 --- a/python/sglang/srt/disaggregation/encode_receiver.py +++ b/python/sglang/srt/disaggregation/encode_receiver.py @@ -399,7 +399,7 @@ class WaitingImageRequest: self.receive_count = receive_count self.num_items_assigned = recv_req.num_items_assigned self.embedding_port, self.recv_socket = get_zmq_socket_on_host( - zmq.Context(), zmq.PULL + zmq.Context(), zmq.PULL, host=host_name ) logger.info(f"Waiting for input {self.embedding_port = }") self.recv_embedding_data = None @@ -681,7 +681,9 @@ class MMReceiverBase(ABC): if len(self.encode_urls) == 0 or not need_wait_for_mm_inputs: return None req_id = uuid.uuid4().hex - embedding_port, recv_socket = get_zmq_socket_on_host(self.context, zmq.PULL) + embedding_port, recv_socket = get_zmq_socket_on_host( + self.context, zmq.PULL, host=self.host + ) mm_data = self._extract_url_data(request_obj) asyncio.create_task( self.encode(req_id, mm_data, embedding_port, "encode", "send") diff --git a/python/sglang/srt/utils/network.py b/python/sglang/srt/utils/network.py index c374c9535..835b06a3c 100644 --- a/python/sglang/srt/utils/network.py +++ b/python/sglang/srt/utils/network.py @@ -189,22 +189,23 @@ def get_zmq_socket_on_host( Args: context: ZeroMQ context to create the socket from. socket_type: Type of ZeroMQ socket to create. - host: Optional host to bind/connect to, without "tcp://" prefix. If None, binds to "tcp://*". + host: Host to bind to, without "tcp://" prefix. Defaults to + "127.0.0.1" (localhost-only) to avoid exposing unauthenticated + sockets to the network (CVE-2026-3060). Callers that need + cross-machine reachability must pass an explicit host. Returns: Tuple of (port, socket) where port is the randomly assigned TCP port. """ socket = context.socket(socket_type) - # Bind to random TCP port, auto-wrapping IPv6 and setting zmq.IPV6 flag config_socket(socket, socket_type) - if host: - if is_valid_ipv6_address(host): - socket.setsockopt(zmq.IPV6, 1) - bind_host = f"tcp://[{host}]" - else: - bind_host = f"tcp://{host}" + if host is None: + host = "127.0.0.1" + if is_valid_ipv6_address(host): + socket.setsockopt(zmq.IPV6, 1) + bind_host = f"tcp://[{host}]" else: - bind_host = "tcp://*" + bind_host = f"tcp://{host}" port = socket.bind_to_random_port(bind_host) return port, socket