Commit 5133811
registry: reject extracted archives with escaping symlinks (#10001)
## Summary
The source-archive download path in `RegistryClient.swift` extracts a
`.zip` (or other format) archive received from a package registry
without post-extract validation. A pre-existing TODO at line 953
acknowledges the gap:
```swift
let archiver = self.archiverProvider(fileSystem)
// TODO: Bail if archive contains relative paths or overlapping files
do {
try await archiver.extract(from: downloadPath, to: destinationPath)
...
}
```
The same extract pattern in
`Sources/Workspace/Workspace+BinaryArtifacts.swift` (lines
[332](https://github.com/swiftlang/swift-package-manager/blob/main/Sources/Workspace/Workspace+BinaryArtifacts.swift#L332)
and
[484](https://github.com/swiftlang/swift-package-manager/blob/main/Sources/Workspace/Workspace+BinaryArtifacts.swift#L484),
called for binary-artifact zips on PackageManifest URL or Workspace
Resolution path) already invokes `validateNoEscapingSymlinks(in:)` after
every `archiver.extract`. This change brings the registry path to the
same posture.
## Why this matters
Without the guard, an archive entry that encodes a symbolic link with a
target outside the destination directory — for example a stored `evil ->
/Users/<victim>/.ssh` symlink followed by a regular file written through
`evil/authorized_keys` — causes the extractor to write outside the
package's destination directory. That breaks SwiftPM's per-package
containment for any package consumed via the registry intake on first
install, before signing or checksum-TOFU has had a chance to bind a
known-good identity.
## Change
A single line addition right after `archiver.extract`:
```swift
try fileSystem.validateNoEscapingSymlinks(in: destinationPath)
```
plus a comment explaining the symmetry with
`Workspace+BinaryArtifacts.swift`. The TODO comment is removed because
the bail it described is now implemented (the validator throws on any
entry whose symlink target escapes the destination root).
The check runs before the package is installed into the workspace; a
malformed archive surfaces as a registry error and the partial
extraction is removed by the existing `defer { try?
fileSystem.removeFileTree(downloadPath) }`.
## Test plan
No new tests added in this PR — the same guard helper is exercised by
the existing tests in `Tests/WorkspaceTests` for the binary-artifacts
path (`testInvalidArchive`, etc.). I'd be happy to add a registry-flow
integration test that constructs a malicious `.zip` with a symlink entry
pointing outside the destination if the maintainers prefer; let me know.
## Related
- TODO at `RegistryClient.swift:953` is the smoking-gun acknowledgement
of the gap; this PR closes it.
- Mirrors the symmetric guard in
`Workspace+BinaryArtifacts.swift:332,484`.
If this PR ships I'll be happy to follow up with a similar guard on the
binary-artifact registry-download path if it has a separate code site.
---------
Co-authored-by: Ihor Bondarenko <sactransport2000@gmail.com>1 parent 5cd559f commit 5133811
2 files changed
Lines changed: 37 additions & 7 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
950 | 950 | | |
951 | 951 | | |
952 | 952 | | |
953 | | - | |
954 | 953 | | |
955 | 954 | | |
956 | 955 | | |
957 | 956 | | |
958 | 957 | | |
| 958 | + | |
| 959 | + | |
| 960 | + | |
| 961 | + | |
| 962 | + | |
| 963 | + | |
| 964 | + | |
| 965 | + | |
| 966 | + | |
| 967 | + | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
| 977 | + | |
| 978 | + | |
959 | 979 | | |
960 | 980 | | |
961 | 981 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
329 | 329 | | |
330 | 330 | | |
331 | 331 | | |
332 | | - | |
333 | | - | |
334 | | - | |
| 332 | + | |
| 333 | + | |
| 334 | + | |
| 335 | + | |
| 336 | + | |
| 337 | + | |
| 338 | + | |
| 339 | + | |
335 | 340 | | |
336 | 341 | | |
337 | 342 | | |
| |||
481 | 486 | | |
482 | 487 | | |
483 | 488 | | |
484 | | - | |
485 | | - | |
486 | | - | |
| 489 | + | |
| 490 | + | |
| 491 | + | |
| 492 | + | |
| 493 | + | |
| 494 | + | |
| 495 | + | |
| 496 | + | |
487 | 497 | | |
488 | 498 | | |
489 | 499 | | |
| |||
0 commit comments