通过 `load_info` 和 Graphbolt Torch-Data Loading 实现 DGL 中的任意代码执行
Arbitrary Code Execution in DGL via load_info and graphbolt torch-data loading
Affected Component
dgl—dgl.data.utils(load_info),dgl.graphbolt.internal.utils(read_data→_read_torch_data); public APIsdgl.data.utils.load_info(path)—dgl/data/utils.py:342— andread_data(path, fmt, in_memory=True)—dgl/graphbolt/internal/utils.py:40, reached from publicOnDiskDataset.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.loadatdata/utils.py:356; hardcodedtorch.load(..., weights_only=False)atgraphbolt/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@
3d16000b41keeps both sink families;data/lrgb.py:256-1041(LRGBDataset, publicget_idx_split) adds barepickle.load×6 on dataset.pklfiles; 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 graphboltweights_only=Falseliterals were deleted, leaving baretorch.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
# 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 ***内容来源: dmlc/dgl