mirror of
https://github.com/apple/container.git
synced 2026-08-27 02:46:31 +00:00
Validate container ID from XPC requests (#1956)
Signed-off-by: Kathryn Baldauf <k_baldauf@apple.com>
This commit is contained in:
@@ -200,7 +200,9 @@ extension Application {
|
||||
useRosetta ? nil : "--enable-qemu",
|
||||
].compactMap { $0 }
|
||||
|
||||
try ContainerAPIClient.Utility.validEntityName(Builder.builderContainerId)
|
||||
guard ManagedContainer.nameValid(Builder.builderContainerId) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(Builder.builderContainerId) is not a valid container ID")
|
||||
}
|
||||
|
||||
let image = try await ClientImage.fetch(
|
||||
reference: builderImage,
|
||||
|
||||
@@ -70,7 +70,10 @@ extension Application {
|
||||
progress.start()
|
||||
|
||||
let id = Utility.createContainerID(name: self.managementFlags.name)
|
||||
try Utility.validEntityName(id)
|
||||
|
||||
guard ManagedContainer.nameValid(id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(id) is not a valid container ID")
|
||||
}
|
||||
|
||||
let ck = try await Utility.containerConfigFromFlags(
|
||||
id: id,
|
||||
|
||||
@@ -80,7 +80,9 @@ extension Application {
|
||||
}
|
||||
progress.start()
|
||||
|
||||
try Utility.validEntityName(id)
|
||||
guard ManagedContainer.nameValid(id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(id) is not a valid container ID")
|
||||
}
|
||||
|
||||
// Check if container with id already exists.
|
||||
let client = ContainerClient()
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
import ArgumentParser
|
||||
import ContainerAPIClient
|
||||
import ContainerPersistence
|
||||
import ContainerResource
|
||||
import ContainerizationError
|
||||
import ContainerizationOCI
|
||||
import Foundation
|
||||
@@ -115,7 +116,9 @@ extension Application {
|
||||
id = "\(imageName)-\(suffix)"
|
||||
}
|
||||
|
||||
try Utility.validEntityName(id)
|
||||
guard ManagedContainer.nameValid(id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "machine ID \(id) is not a valid machine ID")
|
||||
}
|
||||
|
||||
let client = MachineClient()
|
||||
let (config, resources) = try await MachineClient.machineConfigFromFlags(
|
||||
|
||||
@@ -41,9 +41,12 @@ public struct ManagedContainer: ManagedResource {
|
||||
/// not the protocol's 64-hex default.
|
||||
public static func generateId() -> String { UUID().uuidString.lowercased() }
|
||||
|
||||
/// Container name rule (mixed case, dots, hyphens, underscores). Duplicated from
|
||||
/// Utility.validEntityName
|
||||
/// Container name rule
|
||||
public static func nameValid(_ name: String) -> Bool {
|
||||
// Maximum Linux hostname length is 64, but limit to maximum DNS label length
|
||||
guard name.count <= 63 else {
|
||||
return false
|
||||
}
|
||||
let pattern = #"^[a-zA-Z0-9][a-zA-Z0-9_.-]+$"#
|
||||
return name.range(of: pattern, options: .regularExpression) != nil
|
||||
}
|
||||
|
||||
@@ -56,14 +56,6 @@ public struct Utility {
|
||||
return String(hex.prefix(12))
|
||||
}
|
||||
|
||||
public static func validEntityName(_ name: String) throws {
|
||||
let pattern = #"^[a-zA-Z0-9][a-zA-Z0-9_.-]+$"#
|
||||
let regex = try Regex(pattern)
|
||||
if try regex.firstMatch(in: name) == nil {
|
||||
throw ContainerizationError(.invalidArgument, message: "invalid entity name \(name)")
|
||||
}
|
||||
}
|
||||
|
||||
public static func validMACAddress(_ macAddress: String) throws {
|
||||
let pattern = #"^([0-9A-Fa-f]{2}[:-]){5}([0-9A-Fa-f]{2})$"#
|
||||
let regex = try Regex(pattern)
|
||||
|
||||
@@ -54,6 +54,9 @@ public struct ContainersHarness: Sendable {
|
||||
message: "id cannot be empty"
|
||||
)
|
||||
}
|
||||
guard ManagedContainer.nameValid(id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(id) is not a valid container ID")
|
||||
}
|
||||
let stdio = message.stdio()
|
||||
|
||||
let data = message.dataNoCopy(key: .dynamicEnv)
|
||||
@@ -193,6 +196,9 @@ public struct ContainersHarness: Sendable {
|
||||
options = try JSONDecoder().decode(ContainerCreateOptions.self, from: odata)
|
||||
}
|
||||
let config = try JSONDecoder().decode(ContainerConfiguration.self, from: data)
|
||||
guard ManagedContainer.nameValid(config.id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(config.id) is not a valid container ID")
|
||||
}
|
||||
let kernel = try JSONDecoder().decode(Kernel.self, from: kdata)
|
||||
|
||||
let initImage = message.string(key: .initImage)
|
||||
@@ -262,6 +268,9 @@ public struct ContainersHarness: Sendable {
|
||||
guard let id else {
|
||||
throw ContainerizationError(.invalidArgument, message: "id cannot be empty")
|
||||
}
|
||||
guard ManagedContainer.nameValid(id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(id) is not a valid container ID")
|
||||
}
|
||||
let forceDelete = message.bool(key: .forceDelete)
|
||||
try await service.delete(id: id, force: forceDelete)
|
||||
return message.reply()
|
||||
@@ -272,6 +281,9 @@ public struct ContainersHarness: Sendable {
|
||||
guard let containerId = message.string(key: .id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "id cannot be empty")
|
||||
}
|
||||
guard ManagedContainer.nameValid(containerId) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(containerId) is not a valid container ID")
|
||||
}
|
||||
|
||||
let size = try await service.containerDiskUsage(id: containerId)
|
||||
|
||||
@@ -289,6 +301,9 @@ public struct ContainersHarness: Sendable {
|
||||
message: "id cannot be empty"
|
||||
)
|
||||
}
|
||||
guard ManagedContainer.nameValid(id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(id) is not a valid container ID")
|
||||
}
|
||||
let fds = try await service.logs(id: id)
|
||||
let reply = message.reply()
|
||||
try reply.set(key: .logs, value: fds)
|
||||
@@ -374,6 +389,9 @@ public struct ContainersHarness: Sendable {
|
||||
message: "id cannot be empty"
|
||||
)
|
||||
}
|
||||
guard ManagedContainer.nameValid(id) else {
|
||||
throw ContainerizationError(.invalidArgument, message: "container ID \(id) is not a valid container ID")
|
||||
}
|
||||
let archive = message.string(key: .archive)
|
||||
guard let archive else {
|
||||
throw ContainerizationError(
|
||||
|
||||
@@ -60,6 +60,13 @@ struct ManagedContainerTests {
|
||||
#expect(!ManagedContainer.nameValid("a b"))
|
||||
}
|
||||
|
||||
@Test func nameValidRejectsNamesLongerThan63Characters() {
|
||||
let maxValidName = String(repeating: "a", count: 63)
|
||||
let tooLongName = String(repeating: "a", count: 64)
|
||||
#expect(ManagedContainer.nameValid(maxValidName))
|
||||
#expect(!ManagedContainer.nameValid(tooLongName))
|
||||
}
|
||||
|
||||
@Test func generateIdIsLowercasedUUID() {
|
||||
let id = ManagedContainer.generateId()
|
||||
#expect(id == id.lowercased())
|
||||
|
||||
Reference in New Issue
Block a user