mirror of
https://github.com/utmapp/UTM.git
synced 2026-10-12 02:24:01 +00:00
download: never extract a symlink from a downloaded VM
Bumping ZIPFoundation to 0.9.20 brought in its symlink containment guard, but that guard is not sufficient here. `URL.isContained(in:)` collapses repeated separators in a single pass, so a link target ending in `<package>///../elsewhere` is accepted while the filesystem resolves it to `<package>/../elsewhere`. A crafted archive could pair such a link with a later regular file underneath it, whose own path passes the containment check, and write outside the package again. Nothing in UTM ever stores a symlink inside a package, so reject them outright instead of trying to validate the target. Also remove the destination on failure. Extraction created the .utm directory up front and a mid-loop error left the partial contents in place, where the library would list them as a real virtual machine that survives a relaunch. Assisted-by: Claude:claude-fable-5-1
This commit is contained in:
@@ -85,13 +85,24 @@ class UTMDownloadVMTask: UTMDownloadTask {
|
||||
let destinationURL = destinationFolder.appendingPathComponent(destinationUtmDirectory, isDirectory: true)
|
||||
/// create the .utm directory
|
||||
try fileManager.createDirectory(at: destinationURL, withIntermediateDirectories: false)
|
||||
/// get and extract all files contained in the UTM directory, except the `__MACOSX` folder
|
||||
let containedFiles = archive.filter({ $0.path.hasPrefix(utmFolderInZip.path) && !$0.path.hasSuffix(utmDirectoryEnding) && !$0.path.contains("__MACOSX") })
|
||||
for file in containedFiles {
|
||||
let relativePath = String(file.path.dropFirst(utmFolderInZip.path.count))
|
||||
let isDirectory = file.path.hasSuffix("/")
|
||||
let fileURL = try containedDestination(for: relativePath, in: destinationURL, isDirectory: isDirectory)
|
||||
_ = try archive.extract(file, to: fileURL, skipCRC32: true)
|
||||
do {
|
||||
/// get and extract all files contained in the UTM directory, except the `__MACOSX` folder
|
||||
let containedFiles = archive.filter({ $0.path.hasPrefix(utmFolderInZip.path) && !$0.path.hasSuffix(utmDirectoryEnding) && !$0.path.contains("__MACOSX") })
|
||||
for file in containedFiles {
|
||||
/// we never store a symlink in a package, and a link target that survives a containment
|
||||
/// check can still be resolved outside of the package when it is written through later
|
||||
guard file.type != .symlink else {
|
||||
throw UnzipUnsafePathError()
|
||||
}
|
||||
let relativePath = String(file.path.dropFirst(utmFolderInZip.path.count))
|
||||
let isDirectory = file.path.hasSuffix("/")
|
||||
let fileURL = try containedDestination(for: relativePath, in: destinationURL, isDirectory: isDirectory)
|
||||
_ = try archive.extract(file, to: fileURL, skipCRC32: true)
|
||||
}
|
||||
} catch {
|
||||
/// a partially extracted package would still be picked up as a VM by the library
|
||||
try? fileManager.removeItem(at: destinationURL)
|
||||
throw error
|
||||
}
|
||||
return destinationURL
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user