Repository navigation
Checkpoint config.json data_path is adopted as dataset location - out-of-package file read (path traversal) #59
Description
1. Summary
KnowledgeGraphEmbedding at commit 2e440e0 (current master) adopts the data_path value stored in a checkpoint's config.json whenever --data_path is not given on the command line, and then opens <data_path>/entities.dict without any check that the resolved location stays inside the checkpoint package. When a user evaluates a checkpoint obtained from a third party with the documented "run.py --do_test --init_checkpoint
" command, a crafted config.json directs the loader to arbitrary attacker-chosen filesystem locations outside the package, and the first line of the targeted file is echoed back in the terminal through the int() conversion error at codes/run.py:183. This is a path traversal (CWE-22) in the checkpoint consumption flow. The same unchecked data_path also governs relations.dict, train.txt, valid.txt, test.txt and regions.list reads at codes/run.py:185-216.2. Affected version / environment
DeepGraphLearning/KnowledgeGraphEmbedding, git commit 2e440e0 (2020-06-26, latest commit on master; no tagged releases). Verified on Ubuntu 22.04 (WSL2), Python 3.12.3, torch 2.14.1+cpu, numpy 2.5.2, scikit-learn 1.9.1. The code path is platform-independent Python.
3. Steps to reproduce
- git clone https://github.com/DeepGraphLearning/KnowledgeGraphEmbedding.git && cd KnowledgeGraphEmbedding && git checkout 2e440e0
- Create a stand-in private file outside any package: sudo mkdir -p /victim-private && sudo chown "$USER" /victim-private, then put an entities.dict there whose first line is "TOP-SECRET-LOCAL-ASSET-7734classified-local-ledger" (the attached make_poc.py does all of this automatically and also generates the negative control).
- Build the malicious checkpoint package: a directory containing only config.json with the fields override_config() requires and "data_path": "/victim-private". The attached make_poc.py generates checkpoint/ (malicious) and checkpoint-safe/ (negative control, identical except data_path points at the in-package safe-data/ directory), plus SHA256SUMS.txt.
- From the repository root run the documented test command on the malicious package: python -u codes/run.py --do_test --init_checkpoint /checkpoint
- Observe: the process opens /victim-private/entities.dict (outside the package) and aborts at codes/run.py line 183 with "ValueError: invalid literal for int() with base 10: 'TOP-SECRET-LOCAL-ASSET-7734'", echoing the victim file's content into the terminal.
- Negative control: python -u codes/run.py --do_test --init_checkpoint /checkpoint-safe aborts at the same line but echoes the in-package marker "SAFE-IN-PACKAGE-ID", proving only the consumed file location differs.
- Mitigation check: adding --data_path data/FB15k-237 to the command line suppresses the override (codes/run.py:82); the run then consumes the in-repository dataset and only fails later at the unrelated torch.load of the missing pickle file.
4. Expected behavior
When evaluating a checkpoint directory, the loader should consume only data shipped inside the package or the dataset explicitly given via --data_path. A config.json inside an untrusted checkpoint must not be able to select out-of-package file locations for any open() call.
5. Actual behavior
With no --data_path on the command line, override_config() (codes/run.py:73-88) sets args.data_path from the checkpoint's config.json (lines 82-83) and main() opens os.path.join(args.data_path, 'entities.dict') (lines 179-183) at an arbitrary attacker-chosen location; on int() conversion failure the first line of that file is reflected verbatim in the ValueError message on the terminal and in the test.log written next to the checkpoint.
6. Impact
Confidentiality: low but real - files named entities.dict/relations.dict/train.txt/valid.txt/test.txt at attacker-chosen locations are opened and consumed, and first-line content up to the tab is echoed to the terminal and test.log whenever the first field is non-numeric; victims routinely paste such tracebacks into public channels when asking for help. Integrity: low - evaluation and training silently run on attacker-designated out-of-package data when --data_path is omitted, so reported metrics may be computed on data the user never selected. Availability: none. CVSS v3.1: 3.5 (Low), CVSS:3.1/AV:N/AC:L/PR:N/UI:R/S:U/C:L/I:L/A:N. Note: the unsafe torch.load() of the checkpoint pickle at codes/run.py:272 is a separate concern and is not reached in this chain; it is not covered by this report.
7. Suggested fix
Treat config.json inside a checkpoint as untrusted input: either remove the data_path override in override_config() and require --data_path on the command line when evaluating a checkpoint, or constrain the checkpoint-provided data_path to locations that resolve inside the checkpoint directory and reject absolute paths and ".." segments before any open() call. Workaround for users until patched: always pass --data_path explicitly when evaluating third-party checkpoints.
8. Additional context (PoC, redacted)
Attachments: make_poc.py generates the malicious package (data_path /victim-private), the byte-identical negative control (in-package safe-data), the benign entities.dict and SHA256SUMS.txt; no code files, no pickles and no other executables are included in either package, and the crash occurs before any torch.load() call.
Activity