Files
apple--containerization/Sources/ContainerizationOS/Linux/Capabilities.swift
T
wehub-resource-sync 680845cb1c
Build containerization / Verify commit signatures (push) Has been skipped
Build containerization / containerization (push) Successful in 0s
Linux build / Linux compile check (push) Has been cancelled
Linux build / Determine Swift version (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 12:25:30 +08:00

671 lines
23 KiB
Swift

//===----------------------------------------------------------------------===//
// 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 CShim
#if canImport(FoundationEssentials)
import FoundationEssentials
#else
import Foundation
#endif
// MARK: - Configuration Types
public enum CapabilityParsingError: Swift.Error, CustomStringConvertible {
case invalidCapabilitySet(String)
case invalidCapabilityName(String)
public var description: String {
switch self {
case .invalidCapabilitySet(let value):
return "invalid CapabilitySet value '\(value)'"
case .invalidCapabilityName(let value):
return "invalid CapabilityName '\(value)'"
}
}
}
public struct CapabilitySet: Sendable, Hashable {
private enum Value: Hashable, Sendable, CaseIterable {
case bounding
case effective
case inheritable
case permitted
case ambient
}
private var value: Value
private init(_ value: Value) {
self.value = value
}
public init(rawValue: String) throws {
let values = Value.allCases.reduce(into: [String: Value]()) {
$0[String(describing: $1).lowercased()] = $1
}
guard let match = values[rawValue.lowercased()] else {
throw CapabilityParsingError.invalidCapabilitySet(rawValue)
}
self.value = match
}
public static var bounding: Self { Self(.bounding) }
public static var effective: Self { Self(.effective) }
public static var inheritable: Self { Self(.inheritable) }
public static var permitted: Self { Self(.permitted) }
public static var ambient: Self { Self(.ambient) }
}
extension CapabilitySet: CustomStringConvertible {
public var description: String {
String(describing: self.value)
}
}
public struct CapabilityName: Sendable, Hashable {
private enum Value: Hashable, Sendable, CaseIterable {
case chown
case dacOverride
case dacReadSearch
case fowner
case fsetid
case kill
case setgid
case setuid
case setpcap
case linuxImmutable
case netBindService
case netBroadcast
case netAdmin
case netRaw
case ipcLock
case ipcOwner
case sysModule
case sysRawio
case sysChroot
case sysPtrace
case sysPacct
case sysAdmin
case sysBoot
case sysNice
case sysResource
case sysTime
case sysTtyConfig
case mknod
case lease
case auditWrite
case auditControl
case setfcap
case macOverride
case macAdmin
case syslog
case wakeAlarm
case blockSuspend
case auditRead
case perfmon
case bpf
case checkpointRestore
}
private var value: Value
private init(_ value: Value) {
self.value = value
}
public init(rawValue: String) throws {
let uppercased = rawValue.uppercased()
let normalized = uppercased.hasPrefix("CAP_") ? uppercased : "CAP_\(uppercased)"
let capNameMap: [String: Value] = [
"CAP_CHOWN": .chown,
"CAP_DAC_OVERRIDE": .dacOverride,
"CAP_DAC_READ_SEARCH": .dacReadSearch,
"CAP_FOWNER": .fowner,
"CAP_FSETID": .fsetid,
"CAP_KILL": .kill,
"CAP_SETGID": .setgid,
"CAP_SETUID": .setuid,
"CAP_SETPCAP": .setpcap,
"CAP_LINUX_IMMUTABLE": .linuxImmutable,
"CAP_NET_BIND_SERVICE": .netBindService,
"CAP_NET_BROADCAST": .netBroadcast,
"CAP_NET_ADMIN": .netAdmin,
"CAP_NET_RAW": .netRaw,
"CAP_IPC_LOCK": .ipcLock,
"CAP_IPC_OWNER": .ipcOwner,
"CAP_SYS_MODULE": .sysModule,
"CAP_SYS_RAWIO": .sysRawio,
"CAP_SYS_CHROOT": .sysChroot,
"CAP_SYS_PTRACE": .sysPtrace,
"CAP_SYS_PACCT": .sysPacct,
"CAP_SYS_ADMIN": .sysAdmin,
"CAP_SYS_BOOT": .sysBoot,
"CAP_SYS_NICE": .sysNice,
"CAP_SYS_RESOURCE": .sysResource,
"CAP_SYS_TIME": .sysTime,
"CAP_SYS_TTY_CONFIG": .sysTtyConfig,
"CAP_MKNOD": .mknod,
"CAP_LEASE": .lease,
"CAP_AUDIT_WRITE": .auditWrite,
"CAP_AUDIT_CONTROL": .auditControl,
"CAP_SETFCAP": .setfcap,
"CAP_MAC_OVERRIDE": .macOverride,
"CAP_MAC_ADMIN": .macAdmin,
"CAP_SYSLOG": .syslog,
"CAP_WAKE_ALARM": .wakeAlarm,
"CAP_BLOCK_SUSPEND": .blockSuspend,
"CAP_AUDIT_READ": .auditRead,
"CAP_PERFMON": .perfmon,
"CAP_BPF": .bpf,
"CAP_CHECKPOINT_RESTORE": .checkpointRestore,
]
guard let match = capNameMap[normalized] else {
throw CapabilityParsingError.invalidCapabilityName(rawValue)
}
self.value = match
}
public var capValue: UInt32 {
switch self.value {
case .chown: return 0
case .dacOverride: return 1
case .dacReadSearch: return 2
case .fowner: return 3
case .fsetid: return 4
case .kill: return 5
case .setgid: return 6
case .setuid: return 7
case .setpcap: return 8
case .linuxImmutable: return 9
case .netBindService: return 10
case .netBroadcast: return 11
case .netAdmin: return 12
case .netRaw: return 13
case .ipcLock: return 14
case .ipcOwner: return 15
case .sysModule: return 16
case .sysRawio: return 17
case .sysChroot: return 18
case .sysPtrace: return 19
case .sysPacct: return 20
case .sysAdmin: return 21
case .sysBoot: return 22
case .sysNice: return 23
case .sysResource: return 24
case .sysTime: return 25
case .sysTtyConfig: return 26
case .mknod: return 27
case .lease: return 28
case .auditWrite: return 29
case .auditControl: return 30
case .setfcap: return 31
case .macOverride: return 32
case .macAdmin: return 33
case .syslog: return 34
case .wakeAlarm: return 35
case .blockSuspend: return 36
case .auditRead: return 37
case .perfmon: return 38
case .bpf: return 39
case .checkpointRestore: return 40
}
}
public static var chown: Self { Self(.chown) }
public static var dacOverride: Self { Self(.dacOverride) }
public static var dacReadSearch: Self { Self(.dacReadSearch) }
public static var fowner: Self { Self(.fowner) }
public static var fsetid: Self { Self(.fsetid) }
public static var kill: Self { Self(.kill) }
public static var setgid: Self { Self(.setgid) }
public static var setuid: Self { Self(.setuid) }
public static var setpcap: Self { Self(.setpcap) }
public static var linuxImmutable: Self { Self(.linuxImmutable) }
public static var netBindService: Self { Self(.netBindService) }
public static var netBroadcast: Self { Self(.netBroadcast) }
public static var netAdmin: Self { Self(.netAdmin) }
public static var netRaw: Self { Self(.netRaw) }
public static var ipcLock: Self { Self(.ipcLock) }
public static var ipcOwner: Self { Self(.ipcOwner) }
public static var sysModule: Self { Self(.sysModule) }
public static var sysRawio: Self { Self(.sysRawio) }
public static var sysChroot: Self { Self(.sysChroot) }
public static var sysPtrace: Self { Self(.sysPtrace) }
public static var sysPacct: Self { Self(.sysPacct) }
public static var sysAdmin: Self { Self(.sysAdmin) }
public static var sysBoot: Self { Self(.sysBoot) }
public static var sysNice: Self { Self(.sysNice) }
public static var sysResource: Self { Self(.sysResource) }
public static var sysTime: Self { Self(.sysTime) }
public static var sysTtyConfig: Self { Self(.sysTtyConfig) }
public static var mknod: Self { Self(.mknod) }
public static var lease: Self { Self(.lease) }
public static var auditWrite: Self { Self(.auditWrite) }
public static var auditControl: Self { Self(.auditControl) }
public static var setfcap: Self { Self(.setfcap) }
public static var macOverride: Self { Self(.macOverride) }
public static var macAdmin: Self { Self(.macAdmin) }
public static var syslog: Self { Self(.syslog) }
public static var wakeAlarm: Self { Self(.wakeAlarm) }
public static var blockSuspend: Self { Self(.blockSuspend) }
public static var auditRead: Self { Self(.auditRead) }
public static var perfmon: Self { Self(.perfmon) }
public static var bpf: Self { Self(.bpf) }
public static var checkpointRestore: Self { Self(.checkpointRestore) }
public static var allCases: [CapabilityName] {
Value.allCases.map { CapabilityName($0) }
}
}
extension CapabilityName: CustomStringConvertible {
public var description: String {
switch self.value {
case .chown: return "CAP_CHOWN"
case .dacOverride: return "CAP_DAC_OVERRIDE"
case .dacReadSearch: return "CAP_DAC_READ_SEARCH"
case .fowner: return "CAP_FOWNER"
case .fsetid: return "CAP_FSETID"
case .kill: return "CAP_KILL"
case .setgid: return "CAP_SETGID"
case .setuid: return "CAP_SETUID"
case .setpcap: return "CAP_SETPCAP"
case .linuxImmutable: return "CAP_LINUX_IMMUTABLE"
case .netBindService: return "CAP_NET_BIND_SERVICE"
case .netBroadcast: return "CAP_NET_BROADCAST"
case .netAdmin: return "CAP_NET_ADMIN"
case .netRaw: return "CAP_NET_RAW"
case .ipcLock: return "CAP_IPC_LOCK"
case .ipcOwner: return "CAP_IPC_OWNER"
case .sysModule: return "CAP_SYS_MODULE"
case .sysRawio: return "CAP_SYS_RAWIO"
case .sysChroot: return "CAP_SYS_CHROOT"
case .sysPtrace: return "CAP_SYS_PTRACE"
case .sysPacct: return "CAP_SYS_PACCT"
case .sysAdmin: return "CAP_SYS_ADMIN"
case .sysBoot: return "CAP_SYS_BOOT"
case .sysNice: return "CAP_SYS_NICE"
case .sysResource: return "CAP_SYS_RESOURCE"
case .sysTime: return "CAP_SYS_TIME"
case .sysTtyConfig: return "CAP_SYS_TTY_CONFIG"
case .mknod: return "CAP_MKNOD"
case .lease: return "CAP_LEASE"
case .auditWrite: return "CAP_AUDIT_WRITE"
case .auditControl: return "CAP_AUDIT_CONTROL"
case .setfcap: return "CAP_SETFCAP"
case .macOverride: return "CAP_MAC_OVERRIDE"
case .macAdmin: return "CAP_MAC_ADMIN"
case .syslog: return "CAP_SYSLOG"
case .wakeAlarm: return "CAP_WAKE_ALARM"
case .blockSuspend: return "CAP_BLOCK_SUSPEND"
case .auditRead: return "CAP_AUDIT_READ"
case .perfmon: return "CAP_PERFMON"
case .bpf: return "CAP_BPF"
case .checkpointRestore: return "CAP_CHECKPOINT_RESTORE"
}
}
}
// MARK: - Linux Implementation
#if os(Linux)
#if canImport(Musl)
import Musl
#elseif canImport(Glibc)
import Glibc
#endif
import CShim
/// Capability type flags
public struct CapType: OptionSet, Sendable {
public let rawValue: UInt32
public init(rawValue: UInt32) {
self.rawValue = rawValue
}
// Individual capability sets (for Get/Set/Unset/etc)
public static let effective = CapType(rawValue: 1 << 0)
public static let permitted = CapType(rawValue: 1 << 1)
public static let inheritable = CapType(rawValue: 1 << 2)
public static let bounding = CapType(rawValue: 1 << 3)
public static let ambient = CapType(rawValue: 1 << 4)
// Bulk operation flags (for Apply/Fill/Clear)
public static let caps = CapType(rawValue: 1 << 8) // CAPS - effective, permitted, inheritable
public static let bounds = CapType(rawValue: 1 << 9) // BOUNDS - bounding set
public static let ambs = CapType(rawValue: 1 << 10) // AMBS - ambient capabilities
}
private struct CapabilityHeader {
var version: UInt32
var pid: Int32
init(pid: Int32 = 0) {
self.version = 0x2008_0522
self.pid = pid
}
}
private struct CapabilityData {
var effective1: UInt32
var permitted1: UInt32
var inheritable1: UInt32
var effective2: UInt32
var permitted2: UInt32
var inheritable2: UInt32
init(
effective1: UInt32 = 0,
permitted1: UInt32 = 0,
inheritable1: UInt32 = 0,
effective2: UInt32 = 0,
permitted2: UInt32 = 0,
inheritable2: UInt32 = 0
) {
self.effective1 = effective1
self.permitted1 = permitted1
self.inheritable1 = inheritable1
self.effective2 = effective2
self.permitted2 = permitted2
self.inheritable2 = inheritable2
}
}
/// Interface with Linux capabilities
/// https://linux.die.net/man/7/capabilities
public struct LinuxCapabilities: Sendable {
private var effectiveSet: UInt64 = 0
private var permittedSet: UInt64 = 0
private var inheritableSet: UInt64 = 0
private var boundingSet: UInt64 = 0
private var ambientSet: UInt64 = 0
public init() {}
/// Get the highest supported capability from the kernel
public static func getLastSupported() throws -> CapabilityName {
guard let data = try? String(contentsOfFile: "/proc/sys/kernel/cap_last_cap", encoding: .ascii),
let lastCap = UInt32(data.trimmingCharacters(in: .whitespacesAndNewlines))
else {
throw LinuxCapabilities.Error.invalidCapabilitySet("failed to read /proc/sys/kernel/cap_last_cap")
}
guard let capability = CapabilityName.allCases.first(where: { $0.capValue == lastCap }) else {
throw LinuxCapabilities.Error.invalidCapabilitySet("no capability found for kernel max cap \(lastCap)")
}
return capability
}
/// Set keep caps
public static func setKeepCaps() throws {
let result = CZ_prctl_set_keepcaps()
if result != 0 {
throw LinuxCapabilities.Error.prctlFailed(errno: errno, operation: "PR_SET_KEEPCAPS")
}
}
/// Clear keep caps
public static func clearKeepCaps() throws {
let result = CZ_prctl_clear_keepcaps()
if result != 0 {
throw LinuxCapabilities.Error.prctlFailed(errno: errno, operation: "PR_CLEAR_KEEPCAPS")
}
}
/// Load current process capabilities from kernel
public mutating func load() throws {
let data = try getCurrentCapabilities()
self.effectiveSet = UInt64(data.effective1)
self.permittedSet = UInt64(data.permitted1)
self.inheritableSet = UInt64(data.inheritable1)
}
/// Check if capability is present in the given set
public func get(which: CapType, what: CapabilityName) -> Bool {
let bit = UInt64(1) << what.capValue
if which.contains(.effective) {
return (effectiveSet & bit) != 0
} else if which.contains(.permitted) {
return (permittedSet & bit) != 0
} else if which.contains(.inheritable) {
return (inheritableSet & bit) != 0
} else if which.contains(.bounding) {
return (boundingSet & bit) != 0
} else if which.contains(.ambient) {
return (ambientSet & bit) != 0
}
return false
}
/// Set capabilities in the given sets
public mutating func set(which: CapType, caps: [CapabilityName]) {
let mask = caps.reduce(UInt64(0)) { result, cap in
result | (UInt64(1) << cap.capValue)
}
if which.contains(.effective) {
effectiveSet |= mask
}
if which.contains(.permitted) {
permittedSet |= mask
}
if which.contains(.inheritable) {
inheritableSet |= mask
}
if which.contains(.bounding) {
boundingSet |= mask
}
if which.contains(.ambient) {
ambientSet |= mask
}
}
/// Unset capabilities from the given sets
public mutating func unset(which: CapType, caps: [CapabilityName]) {
let mask = caps.reduce(UInt64(0)) { result, cap in
result | (UInt64(1) << cap.capValue)
}
if which.contains(.effective) {
effectiveSet &= ~mask
}
if which.contains(.permitted) {
permittedSet &= ~mask
}
if which.contains(.inheritable) {
inheritableSet &= ~mask
}
if which.contains(.bounding) {
boundingSet &= ~mask
}
if which.contains(.ambient) {
ambientSet &= ~mask
}
}
/// Fill all bits of given capability types
public mutating func fill(kind: CapType) {
if kind.contains(.caps) {
effectiveSet = 0xFFFF_FFFF_FFFF_FFFF
permittedSet = 0xFFFF_FFFF_FFFF_FFFF
inheritableSet = 0
}
if kind.contains(.bounds) {
boundingSet = 0xFFFF_FFFF_FFFF_FFFF
}
if kind.contains(.ambs) {
ambientSet = 0xFFFF_FFFF_FFFF_FFFF
}
}
/// Clear all bits of given capability types
public mutating func clear(kind: CapType) {
if kind.contains(.caps) {
effectiveSet = 0
permittedSet = 0
inheritableSet = 0
}
if kind.contains(.bounds) {
boundingSet = 0
}
if kind.contains(.ambs) {
ambientSet = 0
}
}
/// Apply capabilities to current process
public func apply(kind: CapType) throws {
// Apply bounding set (requires CAP_SETPCAP)
if kind.contains(.bounds) {
try applyBoundingSet()
}
// Apply main capabilities (effective, permitted, inheritable)
if kind.contains(.caps) {
try applyMainCapabilities()
}
// Apply ambient capabilities
if kind.contains(.ambs) {
try applyAmbientCapabilities()
}
}
private func applyBoundingSet() throws {
let currentData = try getCurrentCapabilities()
let hasSetPCap = (currentData.effective1 & (1 << CapabilityName.setpcap.capValue)) != 0
if hasSetPCap {
// Get the last supported capability to avoid trying to drop unsupported ones
let lastSupported = try Self.getLastSupported()
for cap in CapabilityName.allCases {
// Skip capabilities higher than what the kernel supports
guard cap.capValue <= lastSupported.capValue else { continue }
let capBit = UInt64(1) << cap.capValue
if (boundingSet & capBit) == 0 {
let result = CZ_prctl_capbset_drop(cap.capValue)
if result != 0 && errno != EINVAL {
throw Error.prctlFailed(errno: errno, operation: "PR_CAPBSET_DROP")
}
}
}
}
}
private func applyMainCapabilities() throws {
let data = CapabilityData(
effective1: UInt32(effectiveSet & 0xFFFF_FFFF),
permitted1: UInt32(permittedSet & 0xFFFF_FFFF),
inheritable1: UInt32(inheritableSet & 0xFFFF_FFFF)
)
try setCapabilities(data: data)
}
private func applyAmbientCapabilities() throws {
// Clear all ambient capabilities first
let clearResult = CZ_prctl_cap_ambient_clear_all()
if clearResult != 0 && errno != EINVAL {
throw Error.prctlFailed(errno: errno, operation: "PR_CAP_AMBIENT_CLEAR_ALL")
}
// Get the last supported capability to avoid trying to set unsupported ones
let lastSupported = try Self.getLastSupported()
// Set each ambient capability
for cap in CapabilityName.allCases {
// Skip capabilities higher than what the kernel supports
guard cap.capValue <= lastSupported.capValue else { continue }
let capBit = UInt64(1) << cap.capValue
if (ambientSet & capBit) != 0 {
let result = CZ_prctl_cap_ambient_raise(cap.capValue)
if result != 0 && errno != EINVAL {
throw Error.prctlFailed(errno: errno, operation: "PR_CAP_AMBIENT_RAISE")
}
}
}
}
private func getCurrentCapabilities() throws -> CapabilityData {
var header = CapabilityHeader()
var data = CapabilityData()
let result = withUnsafeMutablePointer(to: &header) { headerPtr in
withUnsafeMutablePointer(to: &data) { dataPtr in
CZ_capget(headerPtr, dataPtr)
}
}
if result != 0 {
throw Error.capgetFailed(errno: errno)
}
return data
}
private func setCapabilities(data: CapabilityData) throws {
var header = CapabilityHeader()
var mutableData = data
let result = withUnsafeMutablePointer(to: &header) { headerPtr in
withUnsafeMutablePointer(to: &mutableData) { dataPtr in
CZ_capset(headerPtr, dataPtr)
}
}
if result != 0 {
throw Error.capsetFailed(errno: errno)
}
}
}
extension LinuxCapabilities {
public enum Error: Swift.Error, CustomStringConvertible {
case unsupportedCapability(name: String)
case capsetFailed(errno: Int32)
case capgetFailed(errno: Int32)
case prctlFailed(errno: Int32, operation: String)
case invalidCapabilitySet(String)
public var description: String {
switch self {
case .unsupportedCapability(let name):
return "unsupported capability: \(name)"
case .capsetFailed(let errno):
return "capset failed with errno \(errno): \(String(cString: strerror(errno)))"
case .capgetFailed(let errno):
return "capget failed with errno \(errno): \(String(cString: strerror(errno)))"
case .prctlFailed(let errno, let operation):
return "prctl(\(operation)) failed with errno \(errno): \(String(cString: strerror(errno)))"
case .invalidCapabilitySet(let message):
return "invalid capability set configuration: \(message)"
}
}
}
}
#endif