OCI image load follows symlinks for `oci-layout` and `index.json` outside the extraction directory
MediumPublished Sep 9, 2026
### Impact An attacker who can get a user to load a crafted OCI image archive (`ImagesService.load(from:force:)`) can cause the loader to import OCI metadata from attacker-chosen paths on the host, outside the archive's own extraction directory. This bypasses the trust boundary that image loading is supposed to enforce: everything the loader reads should come from the archive itself. ### Details `ImagesService.load(from:force:)` extracts the supplied tar archive into a temporary directory and then hands that directory to `ImageStore.load(from:)`. `ArchiveReader.extractEntry()` rejects tar members whose *path* would escape the extraction directory, but it does not restrict where a *symlink entry's target* may point — a symlink named `oci-layout` or `index.json` can be created inside the extraction directory while pointing anywhere on the filesystem the process can reach. `LocalOCILayoutClient.loadIndexFromOCILayout(directory:)` then reads `oci-layout` and `index.json` from that directory using ordinary filesystem calls, which transparently follow symlinks. A crafted archive can therefore redirect the OCI-loading stage to metadata located outside the extracted tree. ### Affected...
Affected versions
| Package | Affected | Fixed in |
|---|---|---|
| apple/container Product | < 1.4.1 | 1.4.1 |
| apple/containerization Product | < 0.45.0 | 0.45.0 |
Details and references
### Impact An attacker who can get a user to load a crafted OCI image archive (`ImagesService.load(from:force:)`) can cause the loader to import OCI metadata from attacker-chosen paths on the host, outside the archive's own extraction directory. This bypasses the trust boundary that image loading is supposed to enforce: everything the loader reads should come from the archive itself. ### Details `ImagesService.load(from:force:)` extracts the supplied tar archive into a temporary directory and then hands that directory to `ImageStore.load(from:)`. `ArchiveReader.extractEntry()` rejects tar members whose *path* would escape the extraction directory, but it does not restrict where a *symlink entry's target* may point — a symlink named `oci-layout` or `index.json` can be created inside the extraction directory while pointing anywhere on the filesystem the process can reach. `LocalOCILayoutClient.loadIndexFromOCILayout(directory:)` then reads `oci-layout` and `index.json` from that directory using ordinary filesystem calls, which transparently follow symlinks. A crafted archive can therefore redirect the OCI-loading stage to metadata located outside the extracted tree. ### Affected code - `ArchiveReader.extractEntry()` — accepts symlink entries without validating their target. - `LocalOCILayoutClient.loadIndexFromOCILayout(directory:)` — reads `oci-layout` and `index.json` via normal filesystem access, following any symlink placed at those paths. ### Mitigations 1. Upgrade to the version of `apple/containerization` containing the fix. 2. Do not load OCI image archives from untrusted sources — an archive supplying `oci-layout`/`index.json` is part of the trust boundary, the same as any other image input. 3. If an untrusted archive has already been loaded on an affected version, treat the resulting image as potentially containing metadata and blobs imported from outside the archive rather than the archive's own contents, and remove it, then prune the local content store, since the imported content can persist there even if the image was never run or pushed: ```bash container image delete <ref> container image prune --all ``` ### Verifying whether you are affected Before loading an archive, check whether any of its top-level `oci-layout` or `index.json` entries are symlinks rather than regular files or directories: ```bash tar -tvf <archive.tar> | grep -E '^l.*(oci-layout|index\.json)' ``` If it has already been extracted, check the same paths in the extraction directory: ```bash find <extracted-dir> -maxdepth 1 \( -name oci-layout -o -name index.json \) -type l ```
- Severity from
- GitHub (reviewed advisory)
More Apple advisories
All Apple| Date | Advisory | Severity | Fixed in |
|---|---|---|---|
| Sep 14 | Apple iOS and iPadOS: out-of-bounds write | Medium4.3 | 26.7+2 more |
| Sep 14 | Apple iOS and iPadOS: denial of service | High7.5 | 18.7.10 |
| Sep 14 | A privacy issue was addressed by moving sensitive data | High7.5 | 26.6 |
| Sep 14 | Apple macOS: improper access control | Medium5.5 | 27 |
| Sep 14 | Apple iOS and iPadOS: improper authentication | High7.1 | 27+2 more |
| Sep 9 | `UnixType.init(path:)` uses a macOS length limit longer than the `sockaddr_un.sun_path` buffer it copies into | Medium | 0.45.0 |