chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,109 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if canImport(FoundationEssentials)
|
||||
import FoundationEssentials
|
||||
#else
|
||||
import Foundation
|
||||
#endif
|
||||
|
||||
#if canImport(Musl)
|
||||
import Musl
|
||||
private let _mount = Musl.mount
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
private let _mount = Glibc.mount
|
||||
#endif
|
||||
|
||||
/// `Binfmt` is a utility type that contains static helpers and types for
|
||||
/// mounting the Linux binfmt_misc filesystem, and creating new binfmt entries.
|
||||
public struct Binfmt: Sendable {
|
||||
/// Default mount path for binfmt_misc.
|
||||
public static let path = "/proc/sys/fs/binfmt_misc"
|
||||
|
||||
/// Entry models a binfmt_misc entry.
|
||||
/// https://docs.kernel.org/admin-guide/binfmt-misc.html
|
||||
public struct Entry {
|
||||
public var name: String
|
||||
public var type: String
|
||||
public var offset: String
|
||||
public var magic: String
|
||||
public var mask: String
|
||||
public var flags: String
|
||||
|
||||
public init(
|
||||
name: String,
|
||||
type: String = "M",
|
||||
offset: String = "",
|
||||
magic: String,
|
||||
mask: String,
|
||||
flags: String = "CF"
|
||||
) {
|
||||
self.name = name
|
||||
self.type = type
|
||||
self.offset = offset
|
||||
self.magic = magic
|
||||
self.mask = mask
|
||||
self.flags = flags
|
||||
}
|
||||
|
||||
/// Returns a binfmt `Entry` for amd64 ELF binaries.
|
||||
public static func amd64() -> Self {
|
||||
Binfmt.Entry(
|
||||
name: "x86_64",
|
||||
magic: #"\x7fELF\x02\x01\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x02\x00\x3e\x00"#,
|
||||
mask: #"\xff\xff\xff\xff\xff\xfe\xfe\x00\xff\xff\xff\xff\xff\xff\xff\xff\xfe\xff\xff\xff"#
|
||||
)
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
/// Register the passed in `binaryPath` as the interpreter for a new binfmt_misc entry.
|
||||
public func register(binaryPath: String) throws {
|
||||
let registration = ":\(self.name):\(self.type):\(self.offset):\(self.magic):\(self.mask):\(binaryPath):\(self.flags)"
|
||||
|
||||
try registration.write(
|
||||
to: URL(fileURLWithPath: Binfmt.path).appendingPathComponent("register"),
|
||||
atomically: false,
|
||||
encoding: .ascii
|
||||
)
|
||||
}
|
||||
|
||||
/// Deregister the binfmt_misc entry described by the current object.
|
||||
public func deregister() throws {
|
||||
let data = "-1"
|
||||
try data.write(
|
||||
to: URL(fileURLWithPath: Binfmt.path).appendingPathComponent(self.name),
|
||||
atomically: false,
|
||||
encoding: .ascii
|
||||
)
|
||||
}
|
||||
#endif // os(Linux)
|
||||
}
|
||||
|
||||
#if os(Linux)
|
||||
/// Crude check to see if /proc/sys/fs/binfmt_misc/register exists.
|
||||
public static func mounted() -> Bool {
|
||||
FileManager.default.fileExists(atPath: "\(Self.path)/register")
|
||||
}
|
||||
|
||||
/// Mount the binfmt_misc filesystem.
|
||||
public static func mount() throws {
|
||||
guard _mount("binfmt_misc", Self.path, "binfmt_misc", 0, "") == 0 else {
|
||||
throw POSIXError.fromErrno()
|
||||
}
|
||||
}
|
||||
#endif // os(Linux)
|
||||
}
|
||||
@@ -0,0 +1,670 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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
|
||||
@@ -0,0 +1,189 @@
|
||||
//===----------------------------------------------------------------------===//
|
||||
// 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.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
#if os(Linux)
|
||||
#if canImport(FoundationEssentials)
|
||||
import FoundationEssentials
|
||||
#else
|
||||
import Foundation
|
||||
#endif
|
||||
|
||||
#if canImport(Musl)
|
||||
import Musl
|
||||
private let _write = Musl.write
|
||||
#elseif canImport(Glibc)
|
||||
import Glibc
|
||||
private let _write = Glibc.write
|
||||
#endif
|
||||
|
||||
import CShim
|
||||
|
||||
// On glibc, epoll constants are EPOLL_EVENTS enum values. On musl they're
|
||||
// plain UInt32. These helpers normalize them to UInt32/Int32.
|
||||
private func epollMask(_ value: UInt32) -> UInt32 { value }
|
||||
private func epollMask(_ value: Int32) -> UInt32 { UInt32(bitPattern: value) }
|
||||
#if canImport(Glibc)
|
||||
private func epollMask(_ value: EPOLL_EVENTS) -> UInt32 { value.rawValue }
|
||||
private func epollFlag(_ value: EPOLL_EVENTS) -> Int32 { Int32(bitPattern: value.rawValue) }
|
||||
#endif
|
||||
|
||||
/// A thin wrapper around the Linux epoll syscall surface.
|
||||
public final class Epoll: Sendable {
|
||||
/// A set of epoll event flags.
|
||||
public struct Mask: OptionSet, Sendable {
|
||||
public let rawValue: UInt32
|
||||
|
||||
public init(rawValue: UInt32) {
|
||||
self.rawValue = rawValue
|
||||
}
|
||||
|
||||
public static let input = Mask(rawValue: epollMask(EPOLLIN))
|
||||
public static let output = Mask(rawValue: epollMask(EPOLLOUT))
|
||||
|
||||
public var isHangup: Bool {
|
||||
!self.isDisjoint(with: Mask(rawValue: epollMask(EPOLLHUP) | epollMask(EPOLLERR)))
|
||||
}
|
||||
|
||||
public var isRemoteHangup: Bool {
|
||||
!self.isDisjoint(with: Mask(rawValue: epollMask(EPOLLRDHUP)))
|
||||
}
|
||||
|
||||
public var readyToRead: Bool {
|
||||
self.contains(.input)
|
||||
}
|
||||
|
||||
public var readyToWrite: Bool {
|
||||
self.contains(.output)
|
||||
}
|
||||
}
|
||||
|
||||
/// An event returned by `wait()`.
|
||||
public struct Event: Sendable {
|
||||
public let fd: Int32
|
||||
public let mask: Mask
|
||||
}
|
||||
|
||||
private let epollFD: Int32
|
||||
private let eventFD: Int32
|
||||
|
||||
public init() throws {
|
||||
let efd = epoll_create1(Int32(EPOLL_CLOEXEC))
|
||||
guard efd >= 0 else {
|
||||
throw POSIXError.fromErrno()
|
||||
}
|
||||
|
||||
let evfd = eventfd(0, Int32(EFD_CLOEXEC | EFD_NONBLOCK))
|
||||
guard evfd >= 0 else {
|
||||
let evfdErrno = POSIXError.fromErrno()
|
||||
close(efd)
|
||||
throw evfdErrno
|
||||
}
|
||||
|
||||
self.epollFD = efd
|
||||
self.eventFD = evfd
|
||||
|
||||
// Register the eventfd with epoll for shutdown signaling.
|
||||
var event = epoll_event()
|
||||
event.events = epollMask(EPOLLIN)
|
||||
event.data.fd = self.eventFD
|
||||
let ctlResult = withUnsafeMutablePointer(to: &event) { ptr in
|
||||
epoll_ctl(efd, EPOLL_CTL_ADD, self.eventFD, ptr)
|
||||
}
|
||||
guard ctlResult == 0 else {
|
||||
let ctlErrno = POSIXError.fromErrno()
|
||||
close(evfd)
|
||||
close(efd)
|
||||
throw ctlErrno
|
||||
}
|
||||
}
|
||||
|
||||
deinit {
|
||||
close(epollFD)
|
||||
close(eventFD)
|
||||
}
|
||||
|
||||
/// Register a file descriptor for edge-triggered monitoring.
|
||||
public func add(_ fd: Int32, mask: Mask) throws {
|
||||
guard fcntl(fd, F_SETFL, O_NONBLOCK) == 0 else {
|
||||
throw POSIXError.fromErrno()
|
||||
}
|
||||
|
||||
let events = epollMask(EPOLLET) | mask.rawValue
|
||||
|
||||
var event = epoll_event()
|
||||
event.events = events
|
||||
event.data.fd = fd
|
||||
|
||||
try withUnsafeMutablePointer(to: &event) { ptr in
|
||||
if epoll_ctl(self.epollFD, EPOLL_CTL_ADD, fd, ptr) == -1 {
|
||||
throw POSIXError.fromErrno()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Remove a file descriptor from the monitored collection.
|
||||
public func delete(_ fd: Int32) throws {
|
||||
var event = epoll_event()
|
||||
let result = withUnsafeMutablePointer(to: &event) { ptr in
|
||||
epoll_ctl(self.epollFD, EPOLL_CTL_DEL, fd, ptr) as Int32
|
||||
}
|
||||
if result != 0 {
|
||||
throw POSIXError.fromErrno()
|
||||
}
|
||||
}
|
||||
|
||||
/// Wait for events.
|
||||
///
|
||||
/// Returns ready events, an empty array on timeout, or `nil` on shutdown.
|
||||
public func wait(maxEvents: Int = 128, timeout: Int32 = -1) -> [Event]? {
|
||||
var events: [epoll_event] = .init(repeating: epoll_event(), count: maxEvents)
|
||||
|
||||
while true {
|
||||
let n = epoll_wait(self.epollFD, &events, Int32(events.count), timeout)
|
||||
if n < 0 {
|
||||
if errno == EINTR || errno == EAGAIN {
|
||||
continue
|
||||
}
|
||||
preconditionFailure("epoll_wait failed unexpectedly: \(POSIXError.fromErrno())")
|
||||
}
|
||||
|
||||
if n == 0 {
|
||||
return []
|
||||
}
|
||||
|
||||
var result: [Event] = []
|
||||
result.reserveCapacity(Int(n))
|
||||
for i in 0..<Int(n) {
|
||||
let fd = events[i].data.fd
|
||||
if fd == self.eventFD {
|
||||
return nil
|
||||
}
|
||||
result.append(Event(fd: fd, mask: Mask(rawValue: events[i].events)))
|
||||
}
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
/// Signal the epoll loop to stop waiting.
|
||||
public func shutdown() {
|
||||
var val: UInt64 = 1
|
||||
let n = _write(eventFD, &val, MemoryLayout<UInt64>.size)
|
||||
precondition(n == MemoryLayout<UInt64>.size, "eventfd write failed: \(POSIXError.fromErrno())")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#endif // os(Linux)
|
||||
Reference in New Issue
Block a user