Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,23 @@
install path embedded in the message (the `ErrorKind` is preserved).
([#112](https://github.com/jaemk/self_update/issues/112))

### Fixed
- Zip extraction (`Extract::extract_into`) now restores symlink entries as real symlinks on unix
instead of writing the link's target path out as a regular file. Materializing the target string
corrupted directory trees that rely on symlinks (for example a macOS `.app` bundle whose
`Frameworks/*/Versions/Current` links are load-bearing for the code signature), so a signed app
extracted from a zip failed to launch. Tar extraction already handled symlinks correctly. A
symlink target that would escape the extraction root (an absolute target, or a relative one whose
`..` components resolve above the destination) is rejected, matching the existing zip-slip defense
on entry names. That per-entry check is lexical, so as a backstop every zip entry's physical
parent is canonicalized after its directories are created and must equal the canonical extraction
root joined with the entry's lexical parent; this rejects a symlinked-parent traversal (an entry
`d/sl -> ..` followed by `d/sl/evil -> ../../x`, lexically in-bounds but physically above the
root) that the lexical check alone cannot catch, while descent through real directories is
unaffected. On windows, where creating symlinks needs elevated privileges, symlink entries
keep the previous regular-file behavior. `Extract::extract_file` now errors on a symlink entry
rather than writing its target string out as the requested file.

### Removed

## [1.0.0-rc.6]
Expand Down
21 changes: 21 additions & 0 deletions specs/ref-update-pipeline.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,27 @@ are `#[non_exhaustive]`; the `Tar` and `Zip` variants are feature-gated on `arch
(`lib.rs:805-885`). The extracted binary is `<tmpdir>/<bin_path>`
(`update.rs:803`).

Zip entry names that would escape `into_dir` are rejected via `enclosed_name` (zip-slip defense,
`lib.rs:1108`). On unix, `extract_into` restores a zip symlink entry (unix mode carrying
`S_IFLNK`, detected by `ZipFile::is_symlink`) as a real symlink rather than writing its target
string out as a regular file (`lib.rs:1132`), preserving symlink-dependent trees such as a macOS
`.app` bundle's `Frameworks/*/Versions/Current` links; `tar` extraction already restores symlinks
itself. A symlink target that escapes the extraction root (absolute, or `..` resolving above
`into_dir`) is rejected by `symlink_target_escapes` with an `Error::Internal`, mirroring the
entry-name defense; a duplicate entry at the link path is removed before the link is created, and
no permission bits are applied to the link. That target check is purely lexical, so it cannot catch
a symlinked intermediate directory that aliases an entry's parent to a shallower physical path (the
symlinked-parent traversal: `d/sl -> ..` then `d/sl/evil -> ../../x`, lexically in-bounds but
physically above the root). As a backstop, the extraction root is canonicalized once up front and,
for every zip entry (symlink or regular file), after its parents are materialized the physical
parent is canonicalized and must equal `canonical_root` joined with the entry's lexical parent;
descent through a symlinked ancestor (or a canonicalize failure) is rejected with an
`Error::Internal`, while descent through real directories is allowed. On non-unix targets the
symlink-restore
block is compiled out and such an entry is written as a regular file (creating symlinks needs
elevated privileges on windows). `extract_file` errors on a symlink entry (`lib.rs:1304`) rather
than writing its target string as the requested file.

### Verify ordering

In `finish_update`, before any extraction or replacement:
Expand Down
4 changes: 4 additions & 0 deletions src/backends/s3.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1724,6 +1724,10 @@ fn add_to_releases_list(releases: &mut Vec<Release>, mut rel: Release) {
mod tests {
use super::Update;
use crate::update::{Release, UpdateConfig};
// Every module-level use of `Duration` here is inside an `s3-auth`-gated test or helper (the
// clamp test brings its own local import), so gate the import to keep a plain `--features s3`
// build free of the unused-import lint.
#[cfg(feature = "s3-auth")]
use std::time::Duration;

// ---------------------------------------------------------------------------
Expand Down
Loading
Loading