mirror of
https://github.com/apple/container.git
synced 2026-08-24 10:05:43 -05:00
Fix potential integer math crash on PublishPort. (#1612)
- Closes #1610. - Discovered, and originally filed as a security advisory, by: PresidentL <131139636+liyander@users.noreply.github.com>. - `PublishPort` currently can store invalid combinations of starting port and range that can overflow UInt16 values when summed, crashing the process. - Updates `PublishPort` to validate inputs on initialization.
This commit is contained in:
@@ -14,6 +14,7 @@
|
||||
// limitations under the License.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
import ContainerizationError
|
||||
import ContainerizationExtras
|
||||
|
||||
/// The network protocols available for port forwarding.
|
||||
@@ -54,12 +55,20 @@ public struct PublishPort: Sendable, Codable {
|
||||
public let count: UInt16
|
||||
|
||||
/// Creates a new port forwarding specification.
|
||||
public init(hostAddress: IPAddress, hostPort: UInt16, containerPort: UInt16, proto: PublishProtocol, count: UInt16) {
|
||||
public init(
|
||||
hostAddress: IPAddress,
|
||||
hostPort: UInt16,
|
||||
containerPort: UInt16,
|
||||
proto: PublishProtocol,
|
||||
count: UInt16
|
||||
) throws {
|
||||
self.hostAddress = hostAddress
|
||||
self.hostPort = hostPort
|
||||
self.containerPort = containerPort
|
||||
self.proto = proto
|
||||
self.count = count
|
||||
try validatePortRange(port: hostPort, count: count)
|
||||
try validatePortRange(port: containerPort, count: count)
|
||||
}
|
||||
|
||||
/// Create a configuration from the supplied Decoder, initializing missing
|
||||
@@ -72,6 +81,14 @@ public struct PublishPort: Sendable, Codable {
|
||||
containerPort = try container.decode(UInt16.self, forKey: .containerPort)
|
||||
proto = try container.decode(PublishProtocol.self, forKey: .proto)
|
||||
count = try container.decodeIfPresent(UInt16.self, forKey: .count) ?? 1
|
||||
try validatePortRange(port: hostPort, count: count)
|
||||
try validatePortRange(port: containerPort, count: count)
|
||||
}
|
||||
|
||||
private func validatePortRange(port: UInt16, count: UInt16) throws {
|
||||
guard count > 0, UInt16.max - port >= count - 1 else {
|
||||
throw ContainerizationError(.invalidArgument, message: "invalid port and count: \(port), \(count)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -79,8 +96,8 @@ extension [PublishPort] {
|
||||
public func hasOverlaps() -> Bool {
|
||||
var hostPorts = Set<String>()
|
||||
for publishPort in self {
|
||||
for index in publishPort.hostPort..<(publishPort.hostPort + publishPort.count) {
|
||||
let hostPortKey = "\(index)/\(publishPort.proto.rawValue)"
|
||||
for offset in 0..<publishPort.count {
|
||||
let hostPortKey = "\(publishPort.hostPort + offset)/\(publishPort.proto.rawValue)"
|
||||
guard !hostPorts.contains(hostPortKey) else {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -701,7 +701,7 @@ public struct Parser {
|
||||
throw ContainerizationError(.invalidArgument, message: "publish host and container port counts are not equal: \(hostPortText):\(containerPortText)")
|
||||
}
|
||||
|
||||
return PublishPort(
|
||||
return try PublishPort(
|
||||
hostAddress: hostAddress,
|
||||
hostPort: hostPortRangeStart,
|
||||
containerPort: containerPortRangeStart,
|
||||
|
||||
@@ -24,8 +24,8 @@ struct PublishPortTests {
|
||||
@Test
|
||||
func testPublishPortsNonOverlapping() throws {
|
||||
let ports = [
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9000, containerPort: 8080, proto: .tcp, count: 100),
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9000, containerPort: 8080, proto: .tcp, count: 100),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),
|
||||
]
|
||||
#expect(!ports.hasOverlaps())
|
||||
}
|
||||
@@ -33,8 +33,8 @@ struct PublishPortTests {
|
||||
@Test
|
||||
func testPublishPortsOverlapping() throws {
|
||||
let ports = [
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9000, containerPort: 8080, proto: .tcp, count: 101),
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9000, containerPort: 8080, proto: .tcp, count: 101),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 9100, containerPort: 8180, proto: .tcp, count: 100),
|
||||
]
|
||||
#expect(ports.hasOverlaps())
|
||||
}
|
||||
@@ -42,11 +42,68 @@ struct PublishPortTests {
|
||||
@Test
|
||||
func testPublishPortsSamePortDifferentProtocols() throws {
|
||||
let ports = [
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 8080, containerPort: 8080, proto: .tcp, count: 1),
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 8080, containerPort: 8080, proto: .udp, count: 1),
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 1024, containerPort: 1024, proto: .tcp, count: 1025),
|
||||
PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 1024, containerPort: 1024, proto: .udp, count: 1025),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 8080, containerPort: 8080, proto: .tcp, count: 1),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 8080, containerPort: 8080, proto: .udp, count: 1),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 1024, containerPort: 1024, proto: .tcp, count: 1025),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 1024, containerPort: 1024, proto: .udp, count: 1025),
|
||||
]
|
||||
#expect(!ports.hasOverlaps())
|
||||
}
|
||||
|
||||
@Test
|
||||
func testPublishPortHostPortOverflowRejected() throws {
|
||||
#expect(throws: (any Error).self) {
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 65535, containerPort: 8080, proto: .tcp, count: 2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testPublishPortContainerPortOverflowRejected() throws {
|
||||
#expect(throws: (any Error).self) {
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 8080, containerPort: 65535, proto: .tcp, count: 2)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testPublishPortZeroCountRejected() throws {
|
||||
#expect(throws: (any Error).self) {
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 8080, containerPort: 8080, proto: .tcp, count: 0)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testPublishPortRangeEndingAtMaxValid() throws {
|
||||
// hostPort 65534 + count 2 → last port 65535, should be accepted
|
||||
_ = try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 65534, containerPort: 8080, proto: .tcp, count: 2)
|
||||
}
|
||||
|
||||
@Test
|
||||
func testPublishPortSingleMaxPortValid() throws {
|
||||
_ = try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 65535, containerPort: 8080, proto: .tcp, count: 1)
|
||||
}
|
||||
|
||||
@Test
|
||||
func testPublishPortDecodeRejectsHostPortOverflow() throws {
|
||||
let json = Data(#"{"hostAddress":"0.0.0.0","hostPort":65535,"containerPort":8080,"proto":"tcp","count":2}"#.utf8)
|
||||
#expect(throws: (any Error).self) {
|
||||
try JSONDecoder().decode(PublishPort.self, from: json)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testPublishPortDecodeRejectsContainerPortOverflow() throws {
|
||||
let json = Data(#"{"hostAddress":"0.0.0.0","hostPort":8080,"containerPort":65535,"proto":"tcp","count":2}"#.utf8)
|
||||
#expect(throws: (any Error).self) {
|
||||
try JSONDecoder().decode(PublishPort.self, from: json)
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
func testHasOverlapsAtMaxPort() throws {
|
||||
let ports = [
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 65534, containerPort: 8080, proto: .tcp, count: 2),
|
||||
try PublishPort(hostAddress: try IPAddress("0.0.0.0"), hostPort: 65534, containerPort: 9090, proto: .tcp, count: 1),
|
||||
]
|
||||
#expect(ports.hasOverlaps())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user