Enabling strict memory safety. (#683)

This commit is contained in:
Max Goedjen 2025-09-09 20:41:29 -07:00 committed by GitHub
parent 6854c05763
commit 8c516e128a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
18 changed files with 55 additions and 55 deletions

View File

@ -90,5 +90,6 @@ var swiftSettings: [PackageDescription.SwiftSetting] {
[ [
.swiftLanguageMode(.v6), .swiftLanguageMode(.v6),
.treatAllWarnings(as: .error), .treatAllWarnings(as: .error),
.strictMemorySafety()
] ]
} }

View File

@ -47,7 +47,7 @@ import XPCWrappers
/// Manually trigger an update check. /// Manually trigger an update check.
public func checkForUpdates() async throws { public func checkForUpdates() async throws {
let session = try XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.SecretiveUpdater") let session = try await XPCTypedSession<[Release], Never>(serviceName: "com.maxgoedjen.Secretive.SecretiveUpdater")
await evaluate(releases: try await session.send()) await evaluate(releases: try await session.send())
session.complete() session.complete()
} }

View File

@ -84,7 +84,7 @@ extension Agent {
} }
logger.log("Agent enumerated \(count) identities") logger.log("Agent enumerated \(count) identities")
var countBigEndian = UInt32(count).bigEndian var countBigEndian = UInt32(count).bigEndian
let countData = Data(bytes: &countBigEndian, count: MemoryLayout<UInt32>.size) let countData = unsafe Data(bytes: &countBigEndian, count: MemoryLayout<UInt32>.size)
return countData + keyData return countData + keyData
} }
@ -150,7 +150,7 @@ extension SSHAgent.Response {
var data: Data { var data: Data {
var raw = self.rawValue var raw = self.rawValue
return Data(bytes: &raw, count: MemoryLayout<UInt8>.size) return unsafe Data(bytes: &raw, count: MemoryLayout<UInt8>.size)
} }
} }

View File

@ -5,8 +5,8 @@ extension FileHandle {
public var pidOfConnectedProcess: Int32 { public var pidOfConnectedProcess: Int32 {
let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<Int32>.size, alignment: 1) let pidPointer = UnsafeMutableRawPointer.allocate(byteCount: MemoryLayout<Int32>.size, alignment: 1)
var len = socklen_t(MemoryLayout<Int32>.size) var len = socklen_t(MemoryLayout<Int32>.size)
getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len) unsafe getsockopt(fileDescriptor, SOCK_STREAM, LOCAL_PEERPID, pidPointer, &len)
return pidPointer.load(as: Int32.self) return unsafe pidPointer.load(as: Int32.self)
} }
} }

View File

@ -29,7 +29,7 @@ final class OpenSSHReader {
let lengthRange = 0..<size let lengthRange = 0..<size
let lengthChunk = remaining[lengthRange] let lengthChunk = remaining[lengthRange]
remaining.removeSubrange(lengthRange) remaining.removeSubrange(lengthRange)
return lengthChunk.bytes.unsafeLoad(as: T.self) return unsafe lengthChunk.bytes.unsafeLoad(as: T.self)
} }
func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String { func readNextChunkAsString(convertEndianness: Bool = true) throws(OpenSSHReaderError) -> String {

View File

@ -2,7 +2,7 @@ import Foundation
import OSLog import OSLog
import SecretKit import SecretKit
public protocol SSHAgentInputParserProtocol: Sendable { public protocol SSHAgentInputParserProtocol {
func parse(data: Data) async throws -> SSHAgent.Request func parse(data: Data) async throws -> SSHAgent.Request
@ -21,7 +21,7 @@ public struct SSHAgentInputParser: SSHAgentInputParserProtocol {
guard data.count > 4 else { guard data.count > 4 else {
throw .invalidData throw .invalidData
} }
let specifiedLength = (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4 let specifiedLength = unsafe (data[0..<4].bytes.unsafeLoad(as: UInt32.self).bigEndian) + 4
let rawRequestInt = data[4] let rawRequestInt = data[4]
let remainingDataRange = 5..<min(Int(specifiedLength), data.count) let remainingDataRange = 5..<min(Int(specifiedLength), data.count)
lazy var body: Data = { Data(data[remainingDataRange]) }() lazy var body: Data = { Data(data[remainingDataRange]) }()

View File

@ -25,11 +25,11 @@ extension SigningRequestTracer {
/// - Parameter pid: The process ID to look up. /// - Parameter pid: The process ID to look up.
/// - Returns: a `kinfo_proc` struct describing the process ID. /// - Returns: a `kinfo_proc` struct describing the process ID.
func pidAndNameInfo(from pid: Int32) -> kinfo_proc { func pidAndNameInfo(from pid: Int32) -> kinfo_proc {
var len = MemoryLayout<kinfo_proc>.size var len = unsafe MemoryLayout<kinfo_proc>.size
let infoPointer = UnsafeMutableRawPointer.allocate(byteCount: len, alignment: 1) let infoPointer = UnsafeMutableRawPointer.allocate(byteCount: len, alignment: 1)
var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid] var name: [Int32] = [CTL_KERN, KERN_PROC, KERN_PROC_PID, pid]
sysctl(&name, UInt32(name.count), infoPointer, &len, nil, 0) unsafe sysctl(&name, UInt32(name.count), infoPointer, &len, nil, 0)
return infoPointer.load(as: kinfo_proc.self) return unsafe infoPointer.load(as: kinfo_proc.self)
} }
/// Generates a ``SecretKit.SigningRequestProvenance.Process`` from a provided process ID. /// Generates a ``SecretKit.SigningRequestProvenance.Process`` from a provided process ID.
@ -37,18 +37,18 @@ extension SigningRequestTracer {
/// - Returns: A ``SecretKit.SigningRequestProvenance.Process`` describing the process. /// - Returns: A ``SecretKit.SigningRequestProvenance.Process`` describing the process.
func process(from pid: Int32) -> SigningRequestProvenance.Process { func process(from pid: Int32) -> SigningRequestProvenance.Process {
var pidAndNameInfo = self.pidAndNameInfo(from: pid) var pidAndNameInfo = self.pidAndNameInfo(from: pid)
let ppid = pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil let ppid = unsafe pidAndNameInfo.kp_eproc.e_ppid != 0 ? pidAndNameInfo.kp_eproc.e_ppid : nil
let procName = withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in let procName = unsafe withUnsafeMutablePointer(to: &pidAndNameInfo.kp_proc.p_comm.0) { pointer in
String(cString: pointer) unsafe String(cString: pointer)
} }
let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN)) let pathPointer = UnsafeMutablePointer<UInt8>.allocate(capacity: Int(MAXPATHLEN))
_ = proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN)) _ = unsafe proc_pidpath(pid, pathPointer, UInt32(MAXPATHLEN))
let path = String(cString: pathPointer) let path = unsafe String(cString: pathPointer)
var secCode: Unmanaged<SecCode>! var secCode: Unmanaged<SecCode>!
let flags: SecCSFlags = [.considerExpiration, .enforceRevocationChecks] let flags: SecCSFlags = [.considerExpiration, .enforceRevocationChecks]
SecCodeCreateWithPID(pid, SecCSFlags(), &secCode) unsafe SecCodeCreateWithPID(pid, SecCSFlags(), &secCode)
let valid = SecCodeCheckValidity(secCode.takeRetainedValue(), flags, nil) == errSecSuccess let valid = unsafe SecCodeCheckValidity(secCode.takeRetainedValue(), flags, nil) == errSecSuccess
return SigningRequestProvenance.Process(pid: pid, processName: procName, appName: appName(for: pid), iconURL: iconURL(for: pid), path: path, validSignature: valid, parentPID: ppid) return SigningRequestProvenance.Process(pid: pid, processName: procName, appName: appName(for: pid), iconURL: iconURL(for: pid), path: path, validSignature: valid, parentPID: ppid)
} }

View File

@ -134,10 +134,10 @@ private extension SocketPort {
convenience init(path: String) { convenience init(path: String) {
var addr = sockaddr_un() var addr = sockaddr_un()
let length = withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in let length = unsafe withUnsafeMutablePointer(to: &addr.sun_path.0) { pointer in
path.withCString { cstring in unsafe path.withCString { cstring in
let len = strlen(cstring) let len = unsafe strlen(cstring)
strncpy(pointer, cstring, len) unsafe strncpy(pointer, cstring, len)
return len return len
} }
} }
@ -147,10 +147,7 @@ private extension SocketPort {
// This mirrors the SUN_LEN macro format. // This mirrors the SUN_LEN macro format.
addr.sun_len = UInt8(MemoryLayout<sockaddr_un>.size - MemoryLayout.size(ofValue: addr.sun_path) + length) addr.sun_len = UInt8(MemoryLayout<sockaddr_un>.size - MemoryLayout.size(ofValue: addr.sun_path) + length)
let data = withUnsafePointer(to: &addr) { pointer in let data = unsafe Data(bytes: &addr, count: MemoryLayout<sockaddr_un>.size)
Data(bytes: pointer, count: MemoryLayout<sockaddr_un>.size)
}
self.init(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)! self.init(protocolFamily: AF_UNIX, socketType: SOCK_STREAM, protocol: 0, address: data)!
} }

View File

@ -36,12 +36,12 @@ public struct KeychainError: Error {
/// A signing-related error. /// A signing-related error.
public struct SigningError: Error { public struct SigningError: Error {
/// The underlying error reported by the API, if one was returned. /// The underlying error reported by the API, if one was returned.
public let error: SecurityError? public let error: CFError?
/// Initializes a SigningError with an optional SecurityError. /// Initializes a SigningError with an optional SecurityError.
/// - Parameter statusCode: The SecurityError, if one is applicable. /// - Parameter statusCode: The SecurityError, if one is applicable.
public init(error: SecurityError?) { public init(error: SecurityError?) {
self.error = error self.error = unsafe error?.takeRetainedValue()
} }
} }

View File

@ -7,7 +7,7 @@ extension Data {
package var lengthAndData: Data { package var lengthAndData: Data {
let rawLength = UInt32(count) let rawLength = UInt32(count)
var endian = rawLength.bigEndian var endian = rawLength.bigEndian
return Data(bytes: &endian, count: MemoryLayout<UInt32>.size) + self return unsafe Data(bytes: &endian, count: MemoryLayout<UInt32>.size) + self
} }
} }

View File

@ -27,7 +27,7 @@ extension SecureEnclave {
kSecReturnAttributes: true kSecReturnAttributes: true
]) ])
var privateUntyped: CFTypeRef? var privateUntyped: CFTypeRef?
SecItemCopyMatching(privateAttributes, &privateUntyped) unsafe SecItemCopyMatching(privateAttributes, &privateUntyped)
guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return } guard let privateTyped = privateUntyped as? [[CFString: Any]] else { return }
let migratedPublicKeys = Set(store.secrets.map(\.publicKey)) let migratedPublicKeys = Set(store.secrets.map(\.publicKey))
var migratedAny = false var migratedAny = false
@ -40,7 +40,7 @@ extension SecureEnclave {
} }
let ref = key[kSecValueRef] as! SecKey let ref = key[kSecValueRef] as! SecKey
let attributes = SecKeyCopyAttributes(ref) as! [CFString: Any] let attributes = SecKeyCopyAttributes(ref) as! [CFString: Any]
let tokenObjectID = attributes[Constants.tokenObjectID] as! Data let tokenObjectID = unsafe attributes[Constants.tokenObjectID] as! Data
let accessControl = attributes[kSecAttrAccessControl] as! SecAccessControl let accessControl = attributes[kSecAttrAccessControl] as! SecAccessControl
// Best guess. // Best guess.
let auth: AuthenticationRequirement = String(describing: accessControl) let auth: AuthenticationRequirement = String(describing: accessControl)

View File

@ -21,7 +21,7 @@ extension SecureEnclave {
/// - duration: The duration of the authorization context, in seconds. /// - duration: The duration of the authorization context, in seconds.
init(secret: Secret, context: LAContext, duration: TimeInterval) { init(secret: Secret, context: LAContext, duration: TimeInterval) {
self.secret = secret self.secret = secret
self.context = context unsafe self.context = context
let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value let durationInNanoSeconds = Measurement(value: duration, unit: UnitDuration.seconds).converted(to: .nanoseconds).value
self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds) self.monotonicExpiration = clock_gettime_nsec_np(CLOCK_MONOTONIC) + UInt64(durationInNanoSeconds)
} }

View File

@ -2,7 +2,7 @@ import Foundation
import Observation import Observation
import Security import Security
import CryptoKit import CryptoKit
@preconcurrency import LocalAuthentication import LocalAuthentication
import SecretKit import SecretKit
import os import os
@ -40,7 +40,7 @@ extension SecureEnclave {
public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data { public func sign(data: Data, with secret: Secret, for provenance: SigningRequestProvenance) async throws -> Data {
var context: LAContext var context: LAContext
if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) { if let existing = await persistentAuthenticationHandler.existingPersistedAuthenticationContext(secret: secret) {
context = existing.context context = unsafe existing.context
} else { } else {
let newContext = LAContext() let newContext = LAContext()
newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name)) newContext.localizedReason = String(localized: .authContextRequestSignatureDescription(appName: provenance.origin.displayName, secretName: secret.name))
@ -57,7 +57,7 @@ extension SecureEnclave {
kSecReturnData: true, kSecReturnData: true,
]) ])
var untyped: CFTypeRef? var untyped: CFTypeRef?
let status = SecItemCopyMatching(queryAttributes, &untyped) let status = unsafe SecItemCopyMatching(queryAttributes, &untyped)
if status != errSecSuccess { if status != errSecSuccess {
throw KeychainError(statusCode: status) throw KeychainError(statusCode: status)
} }
@ -121,12 +121,12 @@ extension SecureEnclave {
fatalError() fatalError()
} }
let access = let access =
SecAccessControlCreateWithFlags(kCFAllocatorDefault, unsafe SecAccessControlCreateWithFlags(kCFAllocatorDefault,
kSecAttrAccessibleWhenUnlockedThisDeviceOnly, kSecAttrAccessibleWhenUnlockedThisDeviceOnly,
flags, flags,
&accessError) &accessError)
if let error = accessError { if let error = unsafe accessError {
throw error.takeRetainedValue() as Error throw unsafe error.takeRetainedValue() as Error
} }
let dataRep: Data let dataRep: Data
let publicKey: Data let publicKey: Data
@ -214,7 +214,7 @@ extension SecureEnclave.Store {
kSecReturnAttributes: true kSecReturnAttributes: true
]) ])
var untyped: CFTypeRef? var untyped: CFTypeRef?
SecItemCopyMatching(queryAttributes, &untyped) unsafe SecItemCopyMatching(queryAttributes, &untyped)
guard let typed = untyped as? [[CFString: Any]] else { return } guard let typed = untyped as? [[CFString: Any]] else { return }
let wrapped: [SecureEnclave.Secret] = typed.compactMap { let wrapped: [SecureEnclave.Secret] = typed.compactMap {
do { do {

View File

@ -1,7 +1,7 @@
import Foundation import Foundation
import Observation import Observation
import Security import Security
@preconcurrency import CryptoTokenKit @unsafe @preconcurrency import CryptoTokenKit
import LocalAuthentication import LocalAuthentication
import SecretKit import SecretKit
@ -70,7 +70,7 @@ extension SmartCard {
kSecReturnRef: true kSecReturnRef: true
]) ])
var untyped: CFTypeRef? var untyped: CFTypeRef?
let status = SecItemCopyMatching(attributes, &untyped) let status = unsafe SecItemCopyMatching(attributes, &untyped)
if status != errSecSuccess { if status != errSecSuccess {
throw KeychainError(statusCode: status) throw KeychainError(statusCode: status)
} }
@ -80,8 +80,8 @@ extension SmartCard {
let key = untypedSafe as! SecKey let key = untypedSafe as! SecKey
var signError: SecurityError? var signError: SecurityError?
guard let algorithm = signatureAlgorithm(for: secret) else { throw UnsupportKeyType() } guard let algorithm = signatureAlgorithm(for: secret) else { throw UnsupportKeyType() }
guard let signature = SecKeyCreateSignature(key, algorithm, data as CFData, &signError) else { guard let signature = unsafe SecKeyCreateSignature(key, algorithm, data as CFData, &signError) else {
throw SigningError(error: signError) throw unsafe SigningError(error: signError)
} }
return signature as Data return signature as Data
} }
@ -152,7 +152,7 @@ extension SmartCard.Store {
kSecReturnAttributes: true kSecReturnAttributes: true
]) ])
var untyped: CFTypeRef? var untyped: CFTypeRef?
SecItemCopyMatching(attributes, &untyped) unsafe SecItemCopyMatching(attributes, &untyped)
guard let typed = untyped as? [[CFString: Any]] else { return } guard let typed = untyped as? [[CFString: Any]] else { return }
let wrapped: [SecretType] = typed.compactMap { let wrapped: [SecretType] = typed.compactMap {
let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret) let name = $0[kSecAttrLabel] as? String ?? String(localized: .unnamedSecret)

View File

@ -1,23 +1,22 @@
import Foundation import Foundation
public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error & Codable>: Sendable { public struct XPCTypedSession<ResponseType: Codable & Sendable, ErrorType: Error & Codable>: ~Copyable {
private nonisolated(unsafe) let connection: NSXPCConnection private let connection: NSXPCConnection
private var proxy: _XPCProtocol private let proxy: _XPCProtocol
public init(serviceName: String, warmup: Bool = false) throws { public init(serviceName: String, warmup: Bool = false) async throws {
connection = NSXPCConnection(serviceName: serviceName) let connection = NSXPCConnection(serviceName: serviceName)
connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self) connection.remoteObjectInterface = NSXPCInterface(with: (any _XPCProtocol).self)
connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6") connection.setCodeSigningRequirement("anchor apple generic and certificate leaf[subject.OU] = Z72PRUAWF6")
connection.resume() connection.resume()
guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() } guard let proxy = connection.remoteObjectProxy as? _XPCProtocol else { fatalError() }
self.connection = connection
self.proxy = proxy self.proxy = proxy
if warmup { if warmup {
Task { [self] in
_ = try? await send() _ = try? await send()
} }
} }
}
public func send(_ message: some Encodable = Data()) async throws -> ResponseType { public func send(_ message: some Encodable = Data()) async throws -> ResponseType {
let encoded = try JSONEncoder().encode(message) let encoded = try JSONEncoder().encode(message)

View File

@ -34,7 +34,7 @@ class AppDelegate: NSObject, NSApplicationDelegate {
func applicationDidFinishLaunching(_ aNotification: Notification) { func applicationDidFinishLaunching(_ aNotification: Notification) {
logger.debug("SecretAgent finished launching") logger.debug("SecretAgent finished launching")
Task { Task {
let inputParser = try XPCAgentInputParser() let inputParser = try await XPCAgentInputParser()
for await session in socketController.sessions { for await session in socketController.sessions {
Task { Task {
do { do {

View File

@ -8,8 +8,8 @@ public final class XPCAgentInputParser: SSHAgentInputParserProtocol {
private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError> private let session: XPCTypedSession<SSHAgent.Request, SSHAgentInputParser.AgentParsingError>
public init() throws { public init() async throws {
session = try XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true) session = try await XPCTypedSession(serviceName: "com.maxgoedjen.Secretive.SecretAgentInputParser", warmup: true)
} }
public func parse(data: Data) async throws -> SSHAgent.Request { public func parse(data: Data) async throws -> SSHAgent.Request {

View File

@ -862,6 +862,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_MEMORY_SAFETY = YES;
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
SWIFT_VERSION = 6.0; SWIFT_VERSION = 6.0;
}; };
@ -930,6 +931,7 @@
SWIFT_COMPILATION_MODE = wholemodule; SWIFT_COMPILATION_MODE = wholemodule;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-O"; SWIFT_OPTIMIZATION_LEVEL = "-O";
SWIFT_STRICT_MEMORY_SAFETY = YES;
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
SWIFT_VERSION = 6.0; SWIFT_VERSION = 6.0;
}; };
@ -1304,6 +1306,7 @@
SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG;
SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_EMIT_LOC_STRINGS = YES;
SWIFT_OPTIMIZATION_LEVEL = "-Onone"; SWIFT_OPTIMIZATION_LEVEL = "-Onone";
SWIFT_STRICT_MEMORY_SAFETY = YES;
SWIFT_TREAT_WARNINGS_AS_ERRORS = YES; SWIFT_TREAT_WARNINGS_AS_ERRORS = YES;
SWIFT_VERSION = 6.0; SWIFT_VERSION = 6.0;
}; };