Security Advisoryapple/container#2261SEVERITY: High

File Descriptor Exhaustion in Apple Container ConnectHandler (DoS) — Acknowledged by Apple

TARGET REPOSITORYapple / container
PATCHED VERSIONSapple/container PR #2260 (commit 56b95bc)
CWE CLASSIFICATION:CWE-400: Uncontrolled Resource Consumption · CWE-775: Missing Release of File Descriptor or Handle after Effective Lifetime
Sep 2026•9 min read•Madhan Alagarsamy
Official Issue & Patch
File Descriptor Exhaustion in Apple Container ConnectHandler (DoS) — Acknowledged by Apple

Executive Summary

During an architecture review of Apple's open-source container engine (apple/container), a critical resource exhaustion flaw was uncovered in the Swift-NIO port-forwarding component (Sources/SocketForwarder/ConnectHandler.swift). When an inbound client connection aborts or disconnects while the backend socket is still being established, the error-handling closure incorrectly invoked context.channel.close() on the already-inactive frontend channel rather than closing the newly allocated backend channel. This left backend sockets open indefinitely, leaking one file descriptor per aborted connection and causing a complete Denial of Service (EMFILE / 'Too many open files').

Coordinated Disclosure Timeline

August 21, 2026:

Identified socket file descriptor leak in ConnectHandler.swift; submitted vulnerability disclosure directly to Apple container team.

Late August 2026:

Apple maintainer (@jglogan) reviewed findings ('Good catch') and requested cross-platform reproduction.

September 2026:

Formulated and submitted a byte-for-byte standalone Swift-NIO reproduction on Linux demonstrating monotonic FD growth (from 25 to 2,387 leaked FDs), alongside a macOS native test script.

September 2026:

Apple maintainer (@egernst) verified the bug: 'I appreciate your digging, and I do think we have a bug to fix here.'

September 2026:

Apple opened public issue #2261 with credit ('Thanks to @madhanalagarsamy for helping identify this bug!') and shipped the official fix in PR #2260.

01.Architecture & ConnectHandler Pipeline

Apple Container uses Apple's Swift-NIO framework for high-performance asynchronous networking. The ConnectHandler is an inbound channel handler responsible for bridging an incoming client socket to a backend container service whenever port-forwarding is enabled (e.g. container run -p 18080:80):

Vulnerable code in Sources/SocketForwarder/ConnectHandler.swift:60-76
swift
ClientBootstrap(group: context.eventLoop)
  .connectTimeout(self.connectTimeout)
  .connect(to: serverAddress)
  .assumeIsolatedUnsafeUnchecked()
  .whenComplete { result in
    switch result {
    case .success(let channel):
      guard context.channel.isActive else {
        self.log?.trace("backend - frontend channel closed, closing backend connection")
        context.channel.close(promise: nil) // BUG: Closes dead frontend instead of newly opened backend `channel`
        return
      }
      self.log?.trace("backend - connected")
      self.glue(channel, context: context)
    case .failure(let error):
      ...
    }
  }

02.Root Cause: Misdirected Channel Closure & FD Leaks

Notice the guard context.channel.isActive branch: when the frontend client disconnects during the connection establishment phase, context.channel is already inactive. Instead of shutting down the newly established backend channel (the socket returned in result), the code called context.channel.close(promise: nil) — closing the already-dead frontend again! The backend socket was orphaned, never closed, and leaked a live file descriptor inside the host process.

Reproduction & Proof of Concept (PoC)

The proof of concept tested rapid connection aborts, proving deterministic, monotonic file descriptor growth until the OS limit is exhausted.

  1. Constructed a standalone Swift-NIO test harness compiling the unmodified SocketForwarder files from apple/container on Linux.
  2. Ran a client script initiating TCP connections to the forwarder port and terminating them immediately before the handshake finished.
  3. Tracked active file descriptors via /proc/self/fd: baseline 25 FDs rose to 270 at 500 iterations, 981 at 2,000 iterations, and 2,387 at 5,000 iterations.
  4. Demonstrated the patch: Changing context.channel.close() to channel.close() resulted in a settled FD count of 24 (delta 0).
  5. Formulated macOS validation script using container run -p 18080:80 and monitoring daemon handle growth with lsof -p $PID.
Rapid connection abort loop (exploit script)
python
#!/usr/bin/env python3
import socket, time

HOST, PORT = "127.0.0.1", 18080
for i in range(1, 5001):
    try:
        s = socket.create_connection((HOST, PORT), timeout=0.5)
        s.close() # Drop client immediately; races against backend connect
    except Exception:
        pass
    if i % 500 == 0:
        print(f"[+] {i} connections aborted")
        time.sleep(0.01)

Impact & Blast Radius

High Severity Denial of Service (CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:N/I:N/A:H). Unauthenticated attackers with network access to forwarded ports can trigger thousands of aborted connections in seconds. Monotonic socket accumulation exhausts the process file descriptor limit (EMFILE / 'Too many open files'), freezing all container port forwarding, crashing active network proxies, and disrupting production workloads until the container daemon is killed and restarted.

Remediation & Mitigation

Apple resolved the issue in Pull Request #2260 by updating ConnectHandler.swift to close the newly connected backend channel (`channel.close(promise: nil)`) instead of the already-inactive frontend `context.channel`.
Apple PR #2260 (commit 56b95bc): Close backend channel immediately if frontend closes
Apple commit 56b95bc diff in ConnectHandler.swift
diff
@@ -64,7 +64,7 @@ extension ConnectHandler {
     case .success(let channel):
       guard context.channel.isActive else {
         self.log?.trace("backend - frontend channel closed, closing backend connection")
-        context.channel.close(promise: nil)
+        channel.close(promise: nil)
         return
       }

References & Documentation

#Apple#Swift NIO#DoS#Resource Leak#CWE-400#CWE-775#Networking#Disclosure