From 1e49e711259880714c28bfa7f540be343d66667f Mon Sep 17 00:00:00 2001 From: Manoj Mahapatra Date: Sun, 8 Feb 2026 13:31:52 -0800 Subject: [PATCH 1/2] Guard against if ipv4Gateway is nil, removed fore unwrap --- .../Containerization/ContainerManager.swift | 9 +- .../ContainerManagerTests.swift | 91 +++++++++++++++++++ 2 files changed, 98 insertions(+), 2 deletions(-) create mode 100644 Tests/ContainerizationTests/ContainerManagerTests.swift diff --git a/Sources/Containerization/ContainerManager.swift b/Sources/Containerization/ContainerManager.swift index 77c43b93..b0bcfc8b 100644 --- a/Sources/Containerization/ContainerManager.swift +++ b/Sources/Containerization/ContainerManager.swift @@ -440,8 +440,13 @@ public struct ContainerManager: Sendable { } if let interface = try self.network?.create(id) { config.interfaces = [interface] - // FIXME: throw instead of crash here if we can't unwrap? - config.dns = .init(nameservers: [interface.ipv4Gateway!.description]) + guard let gateway = interface.ipv4Gateway else { + throw ContainerizationError( + .invalidState, + message: "missing ipv4 gateway for container \(id)" + ) + } + config.dns = .init(nameservers: [gateway.description]) } config.bootLog = BootLog.file(path: self.containerRoot.appendingPathComponent(id).appendingPathComponent("bootlog.log")) try configuration(&config) diff --git a/Tests/ContainerizationTests/ContainerManagerTests.swift b/Tests/ContainerizationTests/ContainerManagerTests.swift new file mode 100644 index 00000000..cf08dfe0 --- /dev/null +++ b/Tests/ContainerizationTests/ContainerManagerTests.swift @@ -0,0 +1,91 @@ +//===----------------------------------------------------------------------===// +// Copyright © 2025-2026 Apple Inc. and the Containerization project authors. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +//===----------------------------------------------------------------------===// + +import Containerization +import ContainerizationArchive +import ContainerizationError +import ContainerizationExtras +import Foundation +import Testing + +@testable import Containerization + +private struct NilGatewayInterface: Interface { + let ipv4Address: CIDRv4 + let ipv4Gateway: IPv4Address? = nil + let macAddress: MACAddress? = nil + + init() { + self.ipv4Address = try! CIDRv4("192.168.64.2/24") + } +} + +private struct NilGatewayNetwork: ContainerManager.Network { + mutating func create(_ id: String) throws -> Interface? { + NilGatewayInterface() + } + + mutating func release(_ id: String) throws {} +} + +@Suite +struct ContainerManagerTests { + @Test func testCreateThrowsWhenGatewayMissing() async throws { + let fm = FileManager.default + let root = fm.uniqueTemporaryDirectory(create: true) + defer { try? fm.removeItem(at: root) } + + let kernelPath = root.appendingPathComponent("vmlinux") + fm.createFile(atPath: kernelPath.path, contents: Data(), attributes: nil) + let initfsPath = root.appendingPathComponent("initfs.ext4") + fm.createFile(atPath: initfsPath.path, contents: Data(), attributes: nil) + + let kernel = Kernel(path: kernelPath, platform: .linuxArm) + let initfs = Mount.block(format: "ext4", source: initfsPath.path, destination: "/") + + var manager = try ContainerManager( + kernel: kernel, + initfs: initfs, + root: root, + network: NilGatewayNetwork() + ) + + let tempDir = fm.uniqueTemporaryDirectory() + defer { try? fm.removeItem(at: tempDir) } + + let tarPath = Foundation.Bundle.module.url(forResource: "scratch", withExtension: "tar")! + let reader = try ArchiveReader(format: .pax, filter: .none, file: tarPath) + let rejectedPaths = try reader.extractContents(to: tempDir) + #expect(rejectedPaths.isEmpty) + + let images = try await manager.imageStore.load(from: tempDir) + let image = images.first! + + let rootfsPath = root.appendingPathComponent("rootfs.ext4") + fm.createFile(atPath: rootfsPath.path, contents: Data(), attributes: nil) + let rootfs = Mount.block(format: "ext4", source: rootfsPath.path, destination: "/") + + do { + _ = try await manager.create("test-nil-gateway", image: image, rootfs: rootfs) { _ in } + #expect(Bool(false), "expected invalidState error for missing ipv4 gateway") + } catch let error as ContainerizationError { + #expect(error.code == .invalidState) + #expect(error.message.contains("missing ipv4 gateway")) + } catch { + #expect(Bool(false), "unexpected error: \(error)") + } + } +} From 6cc974493d726c16e80f7c7a2eb7e23664913d4f Mon Sep 17 00:00:00 2001 From: Manoj Mahapatra Date: Mon, 9 Feb 2026 17:39:14 -0800 Subject: [PATCH 2/2] code formatter --- Tests/ContainerizationTests/ContainerManagerTests.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Tests/ContainerizationTests/ContainerManagerTests.swift b/Tests/ContainerizationTests/ContainerManagerTests.swift index cf08dfe0..293a85ce 100644 --- a/Tests/ContainerizationTests/ContainerManagerTests.swift +++ b/Tests/ContainerizationTests/ContainerManagerTests.swift @@ -1,5 +1,5 @@ //===----------------------------------------------------------------------===// -// Copyright © 2025-2026 Apple Inc. and the Containerization project authors. +// Copyright © 2026 Apple Inc. and the Containerization project authors. // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License.