#7932·dgl

通过 `load_info` 和 Graphbolt Torch-Data Loading 实现 DGL 中的任意代码执行

作者: ez-lbz创建于 2026年8月29日更新于 2026年8月29日

Arbitrary Code Execution in DGL via load_info and graphbolt torch-data loading

Affected Component

  • dgldgl.data.utils (load_info), dgl.graphbolt.internal.utils (read_data_read_torch_data); public APIs dgl.data.utils.load_info(path)dgl/data/utils.py:342 — and read_data(path, fmt, in_memory=True)dgl/graphbolt/internal/utils.py:40, reached from public OnDiskDataset.load()
  • Further referenced sites: utils.py:31, utils.py:40

Affected Versions

  • Runtime-verified on dgl 2.1.0 (official pip wheel), torch 2.1.2+cpu, Python 3.10, Linux; source-verified on repo main HEAD: raw pickle.load at data/utils.py:356; hardcoded torch.load(..., weights_only=False) at graphbolt/internal/utils.py:31, graphbolt/impl/ondisk_dataset.py:855, graphbolt/impl/torch_based_feature_store.py:618, distributed/partition.py:374.
  • Upstream status (2026-08-27): master@3d16000b41 keeps both sink families; data/lrgb.py:256-1041 (LRGBDataset, public get_idx_split) adds bare pickle.load ×6 on dataset .pkl files; the legacy graph_serialize path is a C++-native format, not a pickle sink. Latest Linux-channel release is still 2.1.0. PyPI 2.2.1 (2024-05) ships only a win_amd64 wheel: load_info (:356) unchanged (still RCE); the 4 graphbolt weights_only=False literals were deleted, leaving bare torch.load (effectively fixed on torch ≥ 2.6, still RCE on torch < 2.6); the lrgb.py ×6 sites remain.

Summary

Two independent sinks execute attacker-controlled pickle payloads when a victim loads DGL data files: load_info — a public utility recommended in the DGLDataset docstring for reading dataset info.pkl — calls raw pickle.load on the file; and _read_torch_data calls torch.load with the hardcoded literal weights_only=False, disabling the restricted unpickler torch ≥ 2.6 enables by default. Neither API exposes a safety parameter (load_info(path) takes only the path; read_data's parameters select the format reader and caching), and no environment variable or config file affects either sink (DGLBACKEND selects the tensor backend only). A repo-wide grep for weights_only=True, RestrictedUnpickler, safe_globals, allow_pickle=False returns zero matches across the dgl Python tree. For the graphbolt sink the sole mitigation is torch's global TORCH_FORCE_WEIGHTS_ONLY_LOAD=1 (non-default, breaks legitimate old checkpoints); DGL offers no switch.

Vulnerable Code

python
# dgl/data/utils.py
def load_info(path):                                # :342  (open at :355)
    with open(path, "rb") as pf:
        info = pickle.load(pf)                      # :356  *** RCE sink 1: raw pickle.load ***
    return info
# dgl/graphbolt/internal/utils.py
def read_data(path, fmt, in_memory=True):           # :40
    ...
    return _read_torch_data(path)
def _read_torch_data(path):
    return torch.load(path, weights_only=False)     # :31  *** RCE sink 2: hardcoded literal ***