Skip to content
Open
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
20 changes: 16 additions & 4 deletions Sources/Services/ContainerAPIService/Client/Archiver.swift
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ public final class Archiver: Sendable {
var entryInfo = [ArchiveEntryInfo]()
if !source.isDirectory {
if let info = closure(source) {
try _validatePath(info.pathOnHost, relativeTo: source)
entryInfo.append(info)
}
} else {
Expand All @@ -80,6 +81,8 @@ public final class Archiver: Sendable {
guard let info = closure(url) else {
continue
}
// Path Traversal Güvenlik Kontrolü
try _validatePath(info.pathOnHost, relativeTo: source)
entryInfo.append(info)
}
}
Expand Down Expand Up @@ -109,15 +112,23 @@ public final class Archiver: Sendable {
}

// MARK: private functions
private static func _validatePath(_ url: URL, relativeTo base: URL) throws {
let canonicalURL = url.standardizedFileURL
let canonicalBase = base.standardizedFileURL

// Host yolunun kök baz dizinin dışına çıkıp çıkmadığını denetle
if !canonicalURL.path.hasPrefix(canonicalBase.path) {
throw Error.pathTraversalDetected(url)
}
}

private static func _compressFile(item: URL, entry: WriteEntry, archiver: ArchiveWriter, hasher: inout SHA256) throws {
let writer = archiver.makeTransactionWriter()
let bufferSize = Int(1.mib())
let readBuffer = UnsafeMutablePointer<UInt8>.allocate(capacity: bufferSize)
defer { readBuffer.deallocate() }
try writer.writeHeader(entry: entry)
if entry.fileType == .regular {
// We need to write the data into the archive only if its a regular file
// Symlinks and directories require us to only write the archive header
guard let stream = InputStream(url: item) else {
throw Error.failedToCreateInputStream(item)
}
Expand All @@ -126,7 +137,6 @@ public final class Archiver: Sendable {
while true {
let byteRead = stream.read(readBuffer, maxLength: bufferSize)
if byteRead < 0 {
// stream.read returns -1 on error (e.g. TCC access denial under /Users/)
let streamError = stream.streamError
throw Error.failedToReadFile(item, streamError)
}
Expand Down Expand Up @@ -187,7 +197,6 @@ public final class Archiver: Sendable {
entry.modificationDate = modificationDate
}

// Apply explicit overrides from ArchiveEntryInfo when provided
if let overrideOwner = entryInfo.owner {
entry.owner = overrideOwner
}
Expand Down Expand Up @@ -228,6 +237,7 @@ extension Archiver {
case fileDoesNotExist(_ url: URL)
case failedToCreateInputStream(_ url: URL)
case failedToReadFile(_ url: URL, _ underlying: Swift.Error?)
case pathTraversalDetected(_ url: URL)

public var description: String {
switch self {
Expand All @@ -242,6 +252,8 @@ extension Archiver {
return "failed to read file \(url.path): \(underlying)"
}
return "failed to read file \(url.path)"
case .pathTraversalDetected(let url):
return "path traversal detected for path \(url.path)"
}
}
}
Expand Down