Refuse an absolute-target symlink when unpacking an update part (#1295) #1525

Merged
rockysscenery merged 1 commit from contrib/symlink-link-escape into main 2026-11-10 06:47:04 +00:00
rockysscenery commented 2026-11-10 06:29:33 +00:00 (Migrated from github.com)

What

_unpack() in builder/linux/linux_update.py creates a zip-shipped symbolic link with whatever target the archive names. A relative one lands inside the folder, but an absolute one (evil -> /tmp/evil) lands wherever it says — nothing checked it. This refuses an absolute link target, mirroring how _safe_name already refuses names that leave the folder and _check_part_directory refuses writing through a link. Every link real parts actually ship (the toolchain's) is relative, so the guard only rejects shapes no honest archive uses.

Why

The remaining half of #1295: the earlier hardening waves (7ac1186, b65d80e, 25f5aaf) covered writing through existing links and symlinked staging folders, but a link whose destination itself escapes was still accepted. SHA-256 manifest matching helps but doesn't protect against a malicious archive supplied through a compromised release — which is exactly when an installer shouldn't write wherever the zip tells it to.

Results / Testing performed

  • One guard in _unpack() + one new subtest on test_a_part_cannot_write_through_a_zip_symlink (the authoring fixtures already run cross-platform; this shape needs no symlink privileges anywhere — runs with standard-library Python on any OS). The amended test passes on Windows and Linux.
  • Full tests/test_linux_update.py: same results as main here (symlink-fixture tests need Developer Mode locally; they pass in CI), no new failures from this change.

🤖 Generated with Claude Code

## What `_unpack()` in `builder/linux/linux_update.py` creates a zip-shipped symbolic link with whatever target the archive names. A relative one lands inside the folder, but an absolute one (`evil -> /tmp/evil`) lands wherever it says — nothing checked it. This refuses an absolute link target, mirroring how `_safe_name` already refuses names that leave the folder and `_check_part_directory` refuses writing through a link. Every link real parts actually ship (the toolchain's) is relative, so the guard only rejects shapes no honest archive uses. ## Why The remaining half of #1295: the earlier hardening waves (`7ac1186`, `b65d80e`, `25f5aaf`) covered writing through existing links and symlinked staging folders, but a link whose *destination itself* escapes was still accepted. SHA-256 manifest matching helps but doesn't protect against a malicious archive supplied through a compromised release — which is exactly when an installer shouldn't write wherever the zip tells it to. ## Results / Testing performed - One guard in `_unpack()` + one new subtest on `test_a_part_cannot_write_through_a_zip_symlink` (the authoring fixtures already run cross-platform; this shape needs no symlink privileges anywhere — runs with standard-library Python on any OS). The amended test passes on Windows and Linux. - Full `tests/test_linux_update.py`: same results as main here (symlink-fixture tests need Developer Mode locally; they pass in CI), no new failures from this change. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
Commenting is not possible because the repository is archived.
No description provided.