Skip to content

Unpacking a crafted image layer with an invalid length extended-attribute name crashes the unpacking process

Moderate
jglogan published GHSA-g3rx-2m58-rr63 Aug 30, 2026

Package

swift apple/container (Swift)

Affected versions

<= 1.3.0

Patched versions

> 1.3.0
swift apple/containerization (Swift)
<= 0.41.0
> 0.41.0

Description

Impact

An attacker who can supply a container image layer for a victim to unpack — via a malicious or compromised registry, a poisoned base image, or a pull request that changes which image gets fetched — can crash the process performing the unpack with no privileges beyond getting the victim to pull or run the crafted image reference.

The trigger is a single tar entry carrying an extended-attribute (xattr) name longer than 255 bytes. When ContainerizationEXT4 writes that entry into the ext4 rootfs it is building, an unchecked UInt8 conversion of the name length traps and aborts the process.

Details

EXT4.FileXattrsState.write() writes each xattr entry's name length into a single on-disk byte via an unchecked UInt8 conversion:

// Sources/ContainerizationEXT4/EXT4+Xattrs.swift
out.append(UInt8(attribute.name.count))   // traps if attribute.name.count > 255

Swift's plain UInt8.init(_:) traps at runtime (aborting the process) when the source value does not fit in 8 bits.

attribute.name is the xattr name taken from a tar entry's PAX extended header (SCHILY.xattr.*) with no length limit, after ExtendedAttribute.compressName() strips a known namespace prefix, it flows into EXT4.Formatter.create(...), which calls state.writeInlineAttributes(...) / state.writeBlockAttributes(...), both reaching the conversion above with no length validation along the path.

255 bytes is the maximum length of a single xattr name on a real Linux filesystem, so a layer produced by tarring an actual directory tree can never contain a name this long. The tar format itself imposes no such limit, so a deliberately crafted archive (one that was never the tar of a real filesystem) can contain one.

Affected code

  • EXT4.FileXattrsState.write() in Sources/ContainerizationEXT4/EXT4+Xattrs.swift — fixed to reject a name whose length is 0 or exceeds UInt8.max (255), throwing EXT4.FileXattrsState.Error.invalidXAttr instead of trapping.
  • The reachable callers writeInlineAttributes(...) / writeBlockAttributes(...), invoked from EXT4.Formatter.create(...). Any consumer of ContainerizationEXT4 that formats an ext4 filesystem from untrusted tar/OCI layer input reaches the same conversion.

Mitigations

  1. Upgrade to the version of apple/containerization containing the fix.
  2. Do not unpack, pull, or run container images from untrusted sources — the registry, base image, or CI step that supplies a layer is part of the trust boundary, not just the resulting container's contents.
  3. In CI/CD, avoid pulling or running an image reference that a pull request or other external input can influence on a runner shared with other jobs, since the crash disrupts every concurrent image operation on that host.

Verifying whether you are affected

Inspect a layer tar for any xattr name that would overflow the single length byte before unpacking it. The only reliable check enumerates xattrs through the same library the unpacker uses (ContainerizationArchive, a libarchive wrapper) and applies the same prefix compression (EXT4.ExtendedAttribute.compressName) that feeds the conversion. This catches both PAX xattr encodings libarchive recognizes (SCHILY.xattr. and LIBARCHIVE.xattr., the latter with URL-encoded names) and measures the exact post-compression length that the vulnerable UInt8(...) sees. A plain tarfile/tar scan for long SCHILY.xattr. keys is only an approximation — it misses LIBARCHIVE.xattr.-encoded names and measures the raw, pre-compression length.

Create a Swift project directory and populate it with the following files:

// Sources/main.swift
import Foundation
import ContainerizationArchive
import ContainerizationEXT4

for path in CommandLine.arguments.dropFirst() {
    let reader = try ArchiveReader(file: URL(fileURLWithPath: path))  // auto-detects gzip/zstd/etc.
    for (entry, _) in reader {
        for (rawName, _) in entry.xattrs {
            let name = EXT4.ExtendedAttribute.compressName(rawName).str
            if name.isEmpty || name.utf8.count > 255 {
                print("\(path): \(entry.path ?? "?"): xattr '\(rawName)' -> \(name.utf8.count) bytes")
            }
        }
    }
}
// Package.swift
// swift-tools-version:6.0
import PackageDescription

let package = Package(
    name: "xattr-check",
    platforms: [.macOS("15.0")],
    dependencies: [
        .package(url: "https://github.com/apple/containerization.git", branch: "main")
    ],
    targets: [
        .executableTarget(
            name: "xattr-check",
            dependencies: [
                .product(name: "ContainerizationArchive", package: "containerization"),
                .product(name: "ContainerizationEXT4", package: "containerization"),
            ]
        )
    ]
)

Run the test program against each layer tar file:

swift run xattr-check layer.tar [more-layers.tar ...]

Any output indicates an entry that would trigger the trap on an affected version.

References

Fix: #898, commit 2a331164e9e42fec6192978a153831a0eb9e7ab4

Severity

Moderate

CVE ID

No known CVE

Weaknesses

No CWEs

Credits