| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
@@ -80,20 +80,37 @@ def _sha256(path: Path) -> str: | |||
| 80 | 80 | ||
| 81 | 81 | ||
| 82 | 82 | def _safe_extract(tar_path: Path, dest: Path) -> None: | |
| 83 | - """Extract a tarball, refusing entries that escape ``dest``.""" | ||
| 83 | + """Extract a tarball, refusing entries that escape ``dest``. | ||
| 84 | + | ||
| 85 | + Preflight checks reject unsafe paths, link targets, and special files. | ||
| 86 | + The data filter also checks each member during extraction, accounting | ||
| 87 | + for links created by earlier members and sanitizing file permissions. | ||
| 88 | + """ | ||
| 84 | 89 | dest = dest.resolve() | |
| 85 | 90 | with tarfile.open(tar_path, "r:gz") as tar: | |
| 86 | 91 | members = tar.getmembers() | |
| 87 | 92 | for member in members: | |
| 88 | 93 | target = (dest / member.name).resolve() | |
| 89 | - if not str(target).startswith(str(dest)): | ||
| 94 | + # ``is_relative_to`` compares path components. A string prefix | ||
| 95 | + # test would accept a sibling whose name merely starts with | ||
| 96 | + # ``dest``, such as ``../out-evil/x.txt`` beside ``out/``. | ||
| 97 | + if not target.is_relative_to(dest): | ||
| 90 | 98 | raise RuntimeError(f"Refusing to extract unsafe path: {member.name}") | |
| 91 | - # ``filter='data'`` (3.12+) blocks unsafe members; older Pythons | ||
| 92 | - # fall back to the manual check above. | ||
| 93 | - try: | ||
| 94 | - tar.extractall(dest, filter="data") | ||
| 95 | - except TypeError: | ||
| 96 | - tar.extractall(dest) | ||
| 99 | + if member.issym() or member.islnk(): | ||
| 100 | + # A link's name can be innocuous while its target escapes. | ||
| 101 | + # Symlink targets resolve against the link's own directory; | ||
| 102 | + # hardlink targets are relative to the archive root. | ||
| 103 | + base = target.parent if member.issym() else dest | ||
| 104 | + link_target = (base / member.linkname).resolve() | ||
| 105 | + if not link_target.is_relative_to(dest): | ||
| 106 | + raise RuntimeError(f"Refusing to extract unsafe link: {member.name} -> {member.linkname}") | ||
| 107 | + if member.isdev(): | ||
| 108 | + # The pinned archives only need files, directories, and | ||
| 109 | + # links, so refuse FIFOs and device nodes before extraction. | ||
| 110 | + raise RuntimeError(f"Refusing to extract special file: {member.name}") | ||
| 111 | + # Every supported interpreter provides the data filter. Keep it | ||
| 112 | + # explicit because Python 3.13 defaults to unfiltered extraction. | ||
| 113 | + tar.extractall(dest, filter="data") | ||
| 97 | 114 | ||
| 98 | 115 | ||
| 99 | 116 | def _locate_runtime(extract_root: Path, python_version: str) -> IOSRuntime: | |
| Back | FazBrowse Home | New Git URL |
0 commit comments