Keep Linux update extraction from writing through symlinks #1327
No reviewers
Labels
No labels
accessibility
autorelease: pending
autorelease: tagged
bug
dependencies
documentation
duplicate
enhancement
github_actions
good first issue
help wanted
invalid
question
testing wanted
wontfix
No milestone
No assignees
1 participant
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Hookshot!1327
Loading…
Reference in a new issue
No description provided.
Delete branch "fix/linux-update-zip-symlinks"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
The Linux builder's ZIP extractor checks archive member names but can still write through a symlink placed in the staging tree. Validate the destination and parent directories before extracting, and create regular files exclusively so an earlier entry cannot redirect or be overwritten by a later one.
Keep the symlinks themselves. The v2.0.11 sysroot contains a legitimate absolute
libmvec.solink, and the bundled Python and compiler use relative links.Refs #1295 (Linux ZIP extraction).
Validation
7dbdcdc, the new regression checks reproduced writes through a directory link, a file link, and a destination linked by an earlier part. They pass with this patch.python3 -I -B tests/run_all.py --strict linux_updatepassed.Staging directory
Thanks to @monsterlag for identifying the dangling staging-link case in #1343. Part placement resolves only the staging folder's parent, then checks the whole destination. A temporary-directory test confirms that a dangling staging link stops before download and that an installation with a symlinked parent still works. The official v2.0.11 part install and reuse checks still pass. The two PRs merge together cleanly. #1343 adds the earlier installer check before cleanup or part placement.
Looks good. I walked through the extraction paths and the protection holds up for what a ZIP can actually do:
link/x: the per-entryrealpath(parent) != abspath(parent)check catches it, including a link to.or one that points back inside the tree.link,./link):open(..., "xb")is O_EXCL, so it fails even on a dangling link, andos.symlinkalready refuses to replace an existing entry. Duplicate members are covered too...member names were already rejected by_safe_name, which is now also applied topart.dest. Absolute symlink targets are still allowed, as they need to be forlibmvec.so.On #1343: the overlap is small. With this PR the dangling and live
.partlinks are both rejected in_place(the realpath of the parent only, then the full destination check), so #1343 is no longer needed for safety. What it still adds is failing earlier with a clear message, beforeshutil.rmtree(which otherwise raises its own "Cannot call rmtree on a symbolic link") and beforemakedirs. They touch different hunks and GitHub reports this one as cleanly mergeable, so I'd be fine with both landing, in either order.Residual things, none blocking:
xbcloses the leaf, the parent isn't. That needs a concurrent local writer in the staging tree, so I think it's fine to leave; worth a one-line comment if you want to be explicit._finish_tree(copyfiletoAppRun, icon, desktop file) and theLIST_NAMEwrite ininstallstill follow a link at their destination. A part would have to ship e.g.AppRunas a link at the root of the staging folder, and parts are SHA-checked against the manifest, so this is theoretical. Opening those withxb/lstatfirst would close it, no need to do it here._clone/_clone_treefrom an old version) doesn't_safe_namethepart.filesnames. Those come from the locally stored list, so low risk.Tests are registered via the existing
tests/test_linux_update.pyentry inrun_all.py, the three new tests exercise the real failure modes (and the legit sysroot links), and CI is green.