Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-sglang for openSUSE:Factory checked in at 2026-09-23 14:36:11 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-sglang (Old) and /work/SRC/openSUSE:Factory/.python-sglang.new.383539 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-sglang" Wed Sep 23 14:36:11 2026 rev:4 rq:1379916 version:0.5.20 Changes: -------- --- /work/SRC/openSUSE:Factory/python-sglang/python-sglang.changes 2026-09-08 16:56:48.921728988 +0200 +++ /work/SRC/openSUSE:Factory/.python-sglang.new.383539/python-sglang.changes 2026-09-23 14:37:38.147027789 +0200 @@ -1,0 +2,77 @@ +Tue Sep 22 19:29:07 UTC 2026 - Martin Pluskal <[email protected]> + +- CVE-2026-86793: SafeUnpickler gated pickle deserialization on + a module-prefix allowlist, and a prefix admits every name + below it, leaving builtins.getattr, builtins.__import__ and + torch.storage._load_from_bytes reachable from the + unauthenticated /update_weights_from_tensor endpoint and from + the encoder and weight-cache channels; backport upstream's + two-part fix, which replaces the prefixes with an exact + (module, name) allowlist and serves _load_from_bytes through + torch.load(weights_only=True) with a storage type check + (boo#1280091) + * sglang-safe-unpickler-stdlib-globals.patch, commit + 882577451e (PR 39858) + * sglang-safe-unpickler-explicit-globals.patch, commit + 5b42d10edf (PR 40259) + +------------------------------------------------------------------- +Tue Sep 22 18:17:45 UTC 2026 - Martin Pluskal <[email protected]> + +- Update to 0.5.20: + * /v1/responses no longer stores results unless the server is + started with --enable-response-store; retrieval, + previous_response_id chaining and background requests are + rejected without it + * Prefill context parallelism v1 and its SGLANG_ENABLE_CP_V2 + gate are gone; the strategy-based path is the only one left + * Seventeen deprecated CLI flags are removed, + get_global_server_args() now raises, and ServerArgs is no + longer a dataclass, so dataclasses.asdict on it stops working + * Sampling-mask capacity moves to --sampling-mask-max-tokens + (default 4096); the old environment variable is rejected + * The unified radix tree keeps the sliding-window state where + requests fork from a shared prefix, so branches reuse it + * gRPC: GetIsReady is replaced by a WatchEngineState stream + carrying health, pause state, instance id and discovery + * The Rust multimodal preprocessor's per-request media budget + rises from 256 MiB to 1.25 GiB + * Most of the rest is CUDA, ROCm and NPU kernel work that this + CPU build does not compile +- Rebase sglang-relax-cpu-requirements.patch for the reshuffled + pyproject_cpu.toml; it also drops the newly added torchcodec + dependency, which is not in Factory +- Regenerate vendor.tar.zst + * The crate set advances to rustls 0.23.45, which fixes + RUSTSEC-2026-0285 +- This build is not affected by three open prefill/decode + disaggregation issues: sglang/srt/disaggregation ships intact + and 0.5.20 leaves the code paths unchanged from 0.5.19, but + all three run in KV manager threads that start only under + the prefill or decode --disaggregation-mode (default null), + and no manager is ever constructed - each needs a KV transfer + backend whose transport library is not packaged, so mooncake + raises ImportError as the model runner starts, nixl inside + NixlKVManager, mori already at module import and ascend + without memfabric_hybrid, while fake has no ZeroMQ rank port + and starts no threads + * CVE-2026-94570 (boo#1282324): missing input validation for + AUX_DATA ZeroMQ control messages in the decode worker, read + only by the Mooncake and Mori decode threads + * CVE-2026-93838 (boo#1281937): unbounded memory allocation + in handle_staging_req, called only from the Mooncake and + NIXL decode threads + * CVE-2026-93688 (boo#1281936): unvalidated bootstrap_room + values in the Mooncake prefill bootstrap thread + +------------------------------------------------------------------- +Tue Sep 22 18:14:10 UTC 2026 - Martin Pluskal <[email protected]> + +- CVE-2026-93088: the disaggregated-diffusion orchestrator read + pickles off an unauthenticated bound ZeroMQ socket, allowing + arbitrary code execution; stop installing + sglang/multimodal_gen/runtime/disaggregation (boo#1282323) + * Costs nothing: that runtime imports the unpackaged diffusers + and cache_dit modules, so it could never start here + +------------------------------------------------------------------- Old: ---- sglang-0.5.19.tar.gz New: ---- sglang-0.5.20.tar.gz sglang-safe-unpickler-explicit-globals.patch sglang-safe-unpickler-stdlib-globals.patch ----------(New B)---------- New: 882577451e (PR 39858) * sglang-safe-unpickler-explicit-globals.patch, commit 5b42d10edf (PR 40259) New: (boo#1280091) * sglang-safe-unpickler-stdlib-globals.patch, commit 882577451e (PR 39858) ----------(New E)---------- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-sglang.spec ++++++ --- /var/tmp/diff_new_pack.PkglCD/_old 2026-09-23 14:37:45.054316615 +0200 +++ /var/tmp/diff_new_pack.PkglCD/_new 2026-09-23 14:37:45.056316699 +0200 @@ -17,7 +17,7 @@ Name: python-sglang -Version: 0.5.19 +Version: 0.5.20 Release: 0 Summary: Fast serving framework for large language models # Legal-Review-Notice: sgl-model-gateway and CUDA AOT kernels @@ -48,6 +48,10 @@ Patch2: sglang-cpu-rust-exts.patch # PATCH-FIX-OPENSUSE sglang-grpc-system-protoc.patch -- use system protoc instead of protoc-bin-vendored Patch3: sglang-grpc-system-protoc.patch +# PATCH-FIX-UPSTREAM sglang-safe-unpickler-stdlib-globals.patch boo#1280091 -- CVE-2026-86793: sgl-project/sglang commit 882577451e (PR 39858), replace SafeUnpickler's standard-library module prefixes with exact globals +Patch4: sglang-safe-unpickler-stdlib-globals.patch +# PATCH-FIX-UPSTREAM sglang-safe-unpickler-explicit-globals.patch boo#1280091 -- CVE-2026-86793: sgl-project/sglang commit 5b42d10edf (PR 40259), drop the remaining module prefixes and route torch.storage._load_from_bytes through a weights_only torch.load +Patch5: sglang-safe-unpickler-explicit-globals.patch BuildRequires: %{python_module IPython} BuildRequires: %{python_module Pillow} BuildRequires: %{python_module SoundFile} @@ -227,13 +231,19 @@ pushd python %pyproject_install popd +# multimodal_gen/runtime/disaggregation pickles requests straight off a bound +# ZeroMQ socket with no authentication (CVE-2026-93088). The runtime it belongs +# to needs diffusers and cache_dit, neither of which is packaged, so nothing +# here can reach it -- drop it rather than ship a live gadget. %{python_expand \ rm -rf %{buildroot}%{$python_sitearch}/sglang/test \ %{buildroot}%{$python_sitearch}/sglang/kernels/aot \ %{buildroot}%{$python_sitearch}/sglang/multimodal_gen/test \ + %{buildroot}%{$python_sitearch}/sglang/multimodal_gen/runtime/disaggregation \ %{buildroot}%{$python_sitelib}/sglang/test \ %{buildroot}%{$python_sitelib}/sglang/kernels/aot \ - %{buildroot}%{$python_sitelib}/sglang/multimodal_gen/test + %{buildroot}%{$python_sitelib}/sglang/multimodal_gen/test \ + %{buildroot}%{$python_sitelib}/sglang/multimodal_gen/runtime/disaggregation # CPU flavour never JIT-compiles these; shipping them scores # devel-file-in-non-devel-package (badness 50 each) and fails rpmlint. find %{buildroot} -type f \( \ ++++++ sglang-0.5.19.tar.gz -> sglang-0.5.20.tar.gz ++++++ /work/SRC/openSUSE:Factory/python-sglang/sglang-0.5.19.tar.gz /work/SRC/openSUSE:Factory/.python-sglang.new.383539/sglang-0.5.20.tar.gz differ: char 29, line 1 ++++++ sglang-relax-cpu-requirements.patch ++++++ --- /var/tmp/diff_new_pack.PkglCD/_old 2026-09-23 14:37:45.146320462 +0200 +++ /var/tmp/diff_new_pack.PkglCD/_new 2026-09-23 14:37:45.150320630 +0200 @@ -38,7 +38,7 @@ "orjson", "outlines", "packaging", -@@ -45,31 +42,27 @@ +@@ -45,32 +42,27 @@ "pillow", "prometheus-client>=0.20.0", "psutil", @@ -52,7 +52,7 @@ "scipy", "sentencepiece", "setproctitle", -- "smg-grpc-servicer>=0.5.0", +- "smg-grpc-servicer>=0.9.0", - "soundfile==0.13.1", + "soundfile>=0.13.1", "tabulate", @@ -60,6 +60,7 @@ - "timm==1.0.16", - "torch==2.12.0", - "torchaudio==2.11.0", +- "torchcodec==0.12.0 ; sys_platform != 'linux' or (sys_platform == 'linux' and platform_machine != 'aarch64' and platform_machine != 'arm64' and platform_machine != 'armv7l')", - "torchvision==0.27.0", + "timm>=1.0.16", + "torch>=2.12.0", @@ -77,7 +78,7 @@ "zstandard", ] -@@ -126,6 +119,12 @@ +@@ -128,6 +120,12 @@ "kernels/**/*" ] @@ -90,7 +91,7 @@ [tool.setuptools.packages.find] exclude = [ "assets*", -@@ -134,6 +133,8 @@ +@@ -136,6 +134,8 @@ "dist*", "playground*", "scripts*", @@ -99,7 +100,7 @@ "tests*", ] -@@ -145,6 +146,8 @@ +@@ -147,6 +147,8 @@ "dist*", "playground*", "scripts*", ++++++ sglang-safe-unpickler-explicit-globals.patch ++++++ >From 5b42d10edfa4b626b1e37026577eb5706e89b69a Mon Sep 17 00:00:00 2001 From: Yonghao Zhuang <[email protected]> Date: Fri, 18 Sep 2026 22:38:11 -0700 Subject: [PATCH] fix: restrict SafeUnpickler to explicit globals (#40259) Co-authored-by: yhzhuang <[email protected]> Co-authored-by: Jihui Yang <[email protected]> --- python/sglang/srt/utils/common.py | 228 +++++++++++++++++++++++++----- 1 file changed, 196 insertions(+), 32 deletions(-) diff --git a/python/sglang/srt/utils/common.py b/python/sglang/srt/utils/common.py index 6aed66de2153..5802c11b1bec 100644 --- a/python/sglang/srt/utils/common.py +++ b/python/sglang/srt/utils/common.py @@ -2966,6 +2966,15 @@ def normalize_serialized_named_tensor_payloads( return [normalize_serialized_named_tensor_payload(data) for data in payloads] +def _safe_load_torch_storage(data: bytes): + storage = torch.load(io.BytesIO(data), weights_only=True) + if not isinstance(storage, (torch.storage.TypedStorage, torch.UntypedStorage)): + raise pickle.UnpicklingError( + f"Expected a Torch storage, got {type(storage).__name__}" + ) + return storage + + class SafeUnpickler(pickle.Unpickler): # Standard-library modules expose powerful callables alongside harmless data # types. Keep these globals exact so a newly added callable is denied by @@ -2989,6 +2998,9 @@ class SafeUnpickler(pickle.Unpickler): ("collections", "OrderedDict"), ("collections", "defaultdict"), ("collections", "deque"), + ("collections", "Counter"), + ("copyreg", "__newobj__"), + ("copyreg", "__newobj_ex__"), ("functools", "partial"), ("itertools", "chain"), ("itertools", "repeat"), @@ -2996,41 +3008,193 @@ class SafeUnpickler(pickle.Unpickler): ("multiprocessing.reduction", "_rebuild_socket"), ("multiprocessing.resource_sharer", "DupFd"), ("types", "SimpleNamespace"), - } - - ALLOWED_MODULE_PREFIXES = { - # --- PyTorch types --- - "torch.", - "torch._tensor.", - "torch.storage.", - "torch.nn.parameter.", - "torch.autograd.function.", - # --- torch distributed --- - "torch.distributed.", - "torch.distributed._shard.", - "torch.distributed._composable.", - "torch._C._distributed_c10d.", - "torch._C._distributed_fsdp.", - "torch.distributed.optim.", - # --- PEFT / LoRA --- - "peft.", - "transformers.", - "huggingface_hub.", - # --- SGLang & Unitest --- - "sglang.srt.weight_sync.tensor_bucket.", - "sglang.srt.model_executor.model_runner.", - "sglang.srt.model_executor.model_runner_components.weight_updater.", - "sglang.srt.layers.", - "sglang.srt.utils.", - "sglang.srt.disaggregation.", - "sglang.srt.managers.", - "torch_npu.", + ("_codecs", "encode"), + # --- PyTorch data containers & rebuild functions --- + # Code-module prefixes (torch.*, sglang.srt.*) are NOT allowed: they + # contain gadgets like sglang.srt.utils.common.dynamic_import + ("torch", "Tensor"), + ("torch", "BFloat16Tensor"), + ("torch", "BoolTensor"), + ("torch", "ByteTensor"), + ("torch", "CharTensor"), + ("torch", "DoubleTensor"), + ("torch", "FloatTensor"), + ("torch", "HalfTensor"), + ("torch", "IntTensor"), + ("torch", "LongTensor"), + ("torch", "ShortTensor"), + ("torch.cuda", "BFloat16Tensor"), + ("torch.cuda", "BoolTensor"), + ("torch.cuda", "ByteTensor"), + ("torch.cuda", "CharTensor"), + ("torch.cuda", "DoubleTensor"), + ("torch.cuda", "FloatTensor"), + ("torch.cuda", "HalfTensor"), + ("torch.cuda", "IntTensor"), + ("torch.cuda", "LongTensor"), + ("torch.cuda", "ShortTensor"), + ("torch.cuda.sparse", "BFloat16Tensor"), + ("torch.cuda.sparse", "ByteTensor"), + ("torch.cuda.sparse", "CharTensor"), + ("torch.cuda.sparse", "DoubleTensor"), + ("torch.cuda.sparse", "FloatTensor"), + ("torch.cuda.sparse", "HalfTensor"), + ("torch.cuda.sparse", "IntTensor"), + ("torch.cuda.sparse", "LongTensor"), + ("torch.cuda.sparse", "ShortTensor"), + ("torch.sparse", "BFloat16Tensor"), + ("torch.sparse", "ByteTensor"), + ("torch.sparse", "CharTensor"), + ("torch.sparse", "DoubleTensor"), + ("torch.sparse", "FloatTensor"), + ("torch.sparse", "HalfTensor"), + ("torch.sparse", "IntTensor"), + ("torch.sparse", "LongTensor"), + ("torch.sparse", "ShortTensor"), + ("torch", "Size"), + ("torch", "device"), + ("torch", "dtype"), + ("torch", "bfloat16"), + ("torch", "bit"), + ("torch", "bits16"), + ("torch", "bits1x8"), + ("torch", "bits2x4"), + ("torch", "bits4x2"), + ("torch", "bits8"), + ("torch", "bool"), + ("torch", "cdouble"), + ("torch", "cfloat"), + ("torch", "chalf"), + ("torch", "complex128"), + ("torch", "complex32"), + ("torch", "complex64"), + ("torch", "double"), + ("torch", "float"), + ("torch", "float16"), + ("torch", "float32"), + ("torch", "float4_e2m1fn_x2"), + ("torch", "float64"), + ("torch", "float8_e4m3fn"), + ("torch", "float8_e4m3fnuz"), + ("torch", "float8_e5m2"), + ("torch", "float8_e5m2fnuz"), + ("torch", "float8_e8m0fnu"), + ("torch", "half"), + ("torch", "int"), + ("torch", "int1"), + ("torch", "int16"), + ("torch", "int2"), + ("torch", "int3"), + ("torch", "int32"), + ("torch", "int4"), + ("torch", "int5"), + ("torch", "int6"), + ("torch", "int64"), + ("torch", "int7"), + ("torch", "int8"), + ("torch", "long"), + ("torch", "qint32"), + ("torch", "qint8"), + ("torch", "quint2x4"), + ("torch", "quint4x2"), + ("torch", "quint8"), + ("torch", "short"), + ("torch", "uint1"), + ("torch", "uint16"), + ("torch", "uint2"), + ("torch", "uint3"), + ("torch", "uint32"), + ("torch", "uint4"), + ("torch", "uint5"), + ("torch", "uint6"), + ("torch", "uint64"), + ("torch", "uint7"), + ("torch", "uint8"), + ("torch.nn.parameter", "Parameter"), + ("torch.serialization", "_get_layout"), + ("torch._utils", "_rebuild_tensor"), + ("torch._utils", "_rebuild_tensor_v2"), + ("torch._utils", "_rebuild_tensor_v3"), + ("torch._utils", "_rebuild_parameter"), + ("torch._utils", "_rebuild_parameter_with_state"), + ("torch._utils", "_rebuild_qtensor"), + ("torch._utils", "_rebuild_sparse_tensor"), + ("torch._utils", "_rebuild_meta_tensor_no_storage"), + ("torch._utils", "_rebuild_wrapper_subclass"), + ("torch._utils", "_rebuild_device_tensor_from_numpy"), + ("torch._utils", "_rebuild_device_tensor_from_cpu_tensor"), + ("torch._tensor", "_rebuild_from_type_v2"), + ("torch.storage", "UntypedStorage"), + ("torch.storage", "_UntypedStorage"), + ("torch.storage", "TypedStorage"), + ("torch", "UntypedStorage"), + ("torch", "BFloat16Storage"), + ("torch", "BoolStorage"), + ("torch", "ByteStorage"), + ("torch", "CharStorage"), + ("torch", "ComplexDoubleStorage"), + ("torch", "ComplexFloatStorage"), + ("torch", "DoubleStorage"), + ("torch", "FloatStorage"), + ("torch", "HalfStorage"), + ("torch", "IntStorage"), + ("torch", "LongStorage"), + ("torch", "QInt32Storage"), + ("torch", "QInt8Storage"), + ("torch", "QUInt2x4Storage"), + ("torch", "QUInt4x2Storage"), + ("torch", "QUInt8Storage"), + ("torch", "ShortStorage"), + ("torch.cuda", "BFloat16Storage"), + ("torch.cuda", "BoolStorage"), + ("torch.cuda", "ByteStorage"), + ("torch.cuda", "CharStorage"), + ("torch.cuda", "ComplexDoubleStorage"), + ("torch.cuda", "ComplexFloatStorage"), + ("torch.cuda", "DoubleStorage"), + ("torch.cuda", "FloatStorage"), + ("torch.cuda", "HalfStorage"), + ("torch.cuda", "IntStorage"), + ("torch.cuda", "LongStorage"), + ("torch.cuda", "ShortStorage"), + ("torch.multiprocessing.reductions", "rebuild_tensor"), + ("torch.multiprocessing.reductions", "rebuild_meta_tensor"), + ("torch.multiprocessing.reductions", "rebuild_cuda_tensor"), + ("sglang.srt.utils.patch_torch", "_rebuild_cuda_tensor_modified"), + ("torch_npu.multiprocessing.reductions", "rebuild_npu_tensor"), + ("sglang.srt.utils.patch_torch", "_rebuild_npu_tensor_modified"), + ("torch.multiprocessing.reductions", "rebuild_nested_tensor"), + ("torch.multiprocessing.reductions", "rebuild_sparse_coo_tensor"), + ("torch.multiprocessing.reductions", "rebuild_sparse_compressed_tensor"), + ("torch.multiprocessing.reductions", "rebuild_storage_fd"), + ("torch.multiprocessing.reductions", "rebuild_storage_filename"), + ("torch.multiprocessing.reductions", "rebuild_storage_empty"), + ("torch.multiprocessing.reductions", "rebuild_typed_storage"), + ("torch.multiprocessing.reductions", "rebuild_typed_storage_child"), + ("torch", "per_tensor_affine"), + ("torch", "per_tensor_symmetric"), + ("torch", "per_channel_affine"), + ("torch", "per_channel_symmetric"), + ("torch", "per_channel_affine_float_qparams"), + # --- SGLang data containers only (no code modules) --- + ("sglang.srt.managers.io_struct", "GenerateReqInput"), + ("sglang.srt.managers.io_struct", "EmbeddingReqInput"), + ("sglang.srt.disaggregation.encoder.receiver", "EmbeddingData"), + ("sglang.srt.managers.schedule_batch", "Modality"), + ("sglang.srt.weight_sync.tensor_bucket", "FlattenedTensorMetadata"), + ("sglang.srt.weight_sync.tensor_bucket", "FlattenedTensorBucket"), + ( + "sglang.srt.model_executor.model_runner_components.weight_updater", + "LocalSerializedTensor", + ), + ("sglang.srt.model_executor.model_runner", "LocalSerializedTensor"), } def find_class(self, module, name): - if (module, name) in self.ALLOWED_GLOBALS or any( - (module + ".").startswith(prefix) for prefix in self.ALLOWED_MODULE_PREFIXES - ): + if (module, name) == ("torch.storage", "_load_from_bytes"): + # Torch's helper calls an unrestricted nested torch.load. + return _safe_load_torch_storage + if (module, name) in self.ALLOWED_GLOBALS: return super().find_class(module, name) raise RuntimeError( ++++++ sglang-safe-unpickler-stdlib-globals.patch ++++++ >From 882577451e764a515df2a386a055012e8f075a16 Mon Sep 17 00:00:00 2001 From: Lianmin Zheng <[email protected]> Date: Thu, 17 Sep 2026 01:09:55 -0700 Subject: [PATCH] Restrict SafeUnpickler standard-library globals (#39858) Co-authored-by: jiayisuse <[email protected]> --- python/sglang/srt/utils/common.py | 69 +++++++++--------- .../unit/utils/test_safe_unpickler.py | 72 +++++++++++++++++++ 2 files changed, 105 insertions(+), 36 deletions(-) create mode 100644 test/registered/unit/utils/test_safe_unpickler.py diff --git a/python/sglang/srt/utils/common.py b/python/sglang/srt/utils/common.py index 5509adfdeca0..6aed66de2153 100644 --- a/python/sglang/srt/utils/common.py +++ b/python/sglang/srt/utils/common.py @@ -2967,16 +2967,38 @@ def normalize_serialized_named_tensor_payloads( class SafeUnpickler(pickle.Unpickler): - ALLOWED_MODULE_PREFIXES = { + # Standard-library modules expose powerful callables alongside harmless data + # types. Keep these globals exact so a newly added callable is denied by + # default instead of silently expanding the unpickling attack surface. + ALLOWED_GLOBALS = { # --- Python types --- - "builtins.", - "collections.", - "copyreg.", - "functools.", - "itertools.", - "operator.", - "types.", - "weakref.", + ("builtins", "bool"), + ("builtins", "bytearray"), + ("builtins", "bytes"), + ("builtins", "complex"), + ("builtins", "dict"), + ("builtins", "float"), + ("builtins", "frozenset"), + ("builtins", "int"), + ("builtins", "list"), + ("builtins", "range"), + ("builtins", "set"), + ("builtins", "slice"), + ("builtins", "str"), + ("builtins", "tuple"), + ("collections", "OrderedDict"), + ("collections", "defaultdict"), + ("collections", "deque"), + ("functools", "partial"), + ("itertools", "chain"), + ("itertools", "repeat"), + ("multiprocessing.reduction", "_rebuild_partial"), + ("multiprocessing.reduction", "_rebuild_socket"), + ("multiprocessing.resource_sharer", "DupFd"), + ("types", "SimpleNamespace"), + } + + ALLOWED_MODULE_PREFIXES = { # --- PyTorch types --- "torch.", "torch._tensor.", @@ -2990,10 +3012,6 @@ class SafeUnpickler(pickle.Unpickler): "torch._C._distributed_c10d.", "torch._C._distributed_fsdp.", "torch.distributed.optim.", - # --- multiprocessing --- - "multiprocessing.resource_sharer.", - "multiprocessing.reduction.", - "pickletools.", # --- PEFT / LoRA --- "peft.", "transformers.", @@ -3009,35 +3027,14 @@ class SafeUnpickler(pickle.Unpickler): "torch_npu.", } - DENY_CLASSES = { - ("builtins", "eval"), - ("builtins", "exec"), - ("builtins", "compile"), - ("os", "system"), - ("subprocess", "Popen"), - ("subprocess", "run"), - ("codecs", "decode"), - ("types", "CodeType"), - ("types", "FunctionType"), - } - def find_class(self, module, name): - # Block deterministic attacks - if (module, name) in self.DENY_CLASSES: - raise RuntimeError( - f"Blocked unsafe class loading ({module}.{name}), " - f"to prevent exploitation of CVE-2025-10164" - ) - # Allowlist of safe-to-load modules. - if any( + if (module, name) in self.ALLOWED_GLOBALS or any( (module + ".").startswith(prefix) for prefix in self.ALLOWED_MODULE_PREFIXES ): return super().find_class(module, name) - # Block everything else. (Potential attack surface) raise RuntimeError( - f"Blocked unsafe class loading ({module}.{name}), " - f"to prevent exploitation of CVE-2025-10164" + f"Blocked unsafe global ({module}.{name}) during pickle deserialization" ) diff --git a/test/registered/unit/utils/test_safe_unpickler.py b/test/registered/unit/utils/test_safe_unpickler.py new file mode 100644 index 000000000000..441c5c8b7a67 --- /dev/null +++ b/test/registered/unit/utils/test_safe_unpickler.py @@ -0,0 +1,72 @@ +import pickle +import unittest +from collections import OrderedDict, defaultdict, deque +from functools import partial +from types import SimpleNamespace + +import torch + +from sglang.srt.utils.common import MultiprocessingSerializer, safe_pickle_loads +from sglang.test.ci.ci_register import register_cpu_ci +from sglang.test.test_utils import CustomTestCase + +register_cpu_ci(est_time=2, suite="base-a-test-cpu") + + +class TestSafeUnpickler(CustomTestCase): + def test_rejects_dangerous_builtin_globals(self): + for name in ("__import__", "getattr", "eval", "exec", "compile", "open"): + with ( + self.subTest(name=name), + self.assertRaisesRegex( + RuntimeError, rf"Blocked unsafe global \(builtins\.{name}\)" + ), + ): + # GLOBAL resolves the callable but does not invoke it. This exercises + # the deserialization boundary without constructing an exploit chain. + safe_pickle_loads(f"cbuiltins\n{name}\n.".encode()) + + def test_rejects_unlisted_standard_library_globals(self): + for module, name in ( + ("copyreg", "_reconstructor"), + ("operator", "attrgetter"), + ("types", "FunctionType"), + ): + with ( + self.subTest(module=module, name=name), + self.assertRaisesRegex( + RuntimeError, rf"Blocked unsafe global \({module}\.{name}\)" + ), + ): + safe_pickle_loads(f"c{module}\n{name}\n.".encode()) + + def test_round_trips_safe_standard_library_types(self): + value = SimpleNamespace( + values=OrderedDict([("items", deque([1, 2]))]), + factory=defaultdict(list, {"items": [3]}), + index=slice(1, 4), + parser=partial(int, base=10), + ) + + restored = safe_pickle_loads( + pickle.dumps(value, protocol=pickle.HIGHEST_PROTOCOL) + ) + + self.assertEqual(restored.values, value.values) + self.assertEqual(restored.factory, value.factory) + self.assertEqual(restored.index, value.index) + self.assertEqual(restored.parser("11"), 11) + + def test_round_trips_tensor_payload(self): + value = [("weight", torch.arange(6).reshape(2, 3))] + + restored = MultiprocessingSerializer.deserialize( + MultiprocessingSerializer.serialize(value) + ) + + self.assertEqual(restored[0][0], "weight") + self.assertTrue(torch.equal(restored[0][1], value[0][1])) + + +if __name__ == "__main__": + unittest.main() ++++++ vendor.tar.zst ++++++ /work/SRC/openSUSE:Factory/python-sglang/vendor.tar.zst /work/SRC/openSUSE:Factory/.python-sglang.new.383539/vendor.tar.zst differ: char 7, line 1
