Fix XPC validation
This commit is contained in:
@@ -53,14 +53,12 @@
|
||||
}
|
||||
|
||||
func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||
let allowedBundleIDs = [AppConfiguration.packageName + ".standalone"]
|
||||
guard XPCConnectionValidator.validateConnection(
|
||||
newConnection,
|
||||
teamID: AppConfiguration.teamID,
|
||||
allowedBundleIDs: allowedBundleIDs
|
||||
) else {
|
||||
let info = XPCConnectionValidator.getConnectionInfo(newConnection)
|
||||
logger.warning("Rejected XPC connection: pid=\(info.pid), bundleID=\(info.bundleID ?? "unknown"), teamID=\(info.teamID ?? "unknown")")
|
||||
let bundleID = AppConfiguration.packageName + ".standalone"
|
||||
let requirement = "identifier \"\(bundleID)\" and anchor apple generic and certificate leaf[subject.OU] = \"\(AppConfiguration.teamID)\""
|
||||
do {
|
||||
try newConnection.setCodeSigningRequirement(requirement)
|
||||
} catch {
|
||||
logger.warning("Rejected XPC connection: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
#if os(macOS)
|
||||
import Foundation
|
||||
import os
|
||||
import ServiceManagement
|
||||
|
||||
private let logger = Logger(category: "HelperServiceManager")
|
||||
|
||||
public enum HelperServiceManager {
|
||||
private static var rootHelperService: SMAppService {
|
||||
SMAppService.daemon(plistName: "\(AppConfiguration.rootHelperBundleID).plist")
|
||||
@@ -21,5 +24,25 @@
|
||||
public static func unregisterRootHelper() throws {
|
||||
try rootHelperService.unregister()
|
||||
}
|
||||
|
||||
public static func updateRootHelperIfNeeded() async {
|
||||
guard rootHelperStatus == .enabled else { return }
|
||||
|
||||
do {
|
||||
let installedVersion = try RootHelperClient.shared.getVersion()
|
||||
let currentVersion = Bundle.main.version
|
||||
guard currentVersion != installedVersion else { return }
|
||||
} catch {
|
||||
logger.warning("Failed to get root helper version, updating: \(error.localizedDescription)")
|
||||
}
|
||||
|
||||
do {
|
||||
try unregisterRootHelper()
|
||||
try await Task.sleep(for: .seconds(1))
|
||||
try registerRootHelper()
|
||||
} catch {
|
||||
logger.error("Failed to update root helper: \(error.localizedDescription)")
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -42,6 +42,7 @@
|
||||
|
||||
func getWorkingDirectorySize(reply: @escaping (Int64, NSError?) -> Void)
|
||||
func cleanWorkingDirectory(reply: @escaping (NSError?) -> Void)
|
||||
func getVersion(reply: @escaping (String) -> Void)
|
||||
}
|
||||
|
||||
public enum RootHelperXPC {
|
||||
@@ -217,5 +218,52 @@
|
||||
proxy.cleanWorkingDirectory(reply: reply)
|
||||
}
|
||||
}
|
||||
|
||||
public func getVersion() throws -> String {
|
||||
let semaphore = DispatchSemaphore(value: 0)
|
||||
var result: String?
|
||||
var resultError: NSError?
|
||||
|
||||
let conn = getConnection()
|
||||
guard let proxy = conn.remoteObjectProxyWithErrorHandler({ error in
|
||||
logger.error("getVersion XPC error: \(error.localizedDescription)")
|
||||
resultError = error as NSError
|
||||
semaphore.signal()
|
||||
}) as? RootHelperProtocol else {
|
||||
connectionLock.lock()
|
||||
connection = nil
|
||||
connectionLock.unlock()
|
||||
conn.invalidate()
|
||||
throw NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "Failed to get RootHelper proxy",
|
||||
])
|
||||
}
|
||||
|
||||
proxy.getVersion { version in
|
||||
result = version
|
||||
semaphore.signal()
|
||||
}
|
||||
|
||||
let timeout = DispatchTime.now() + .seconds(5)
|
||||
if semaphore.wait(timeout: timeout) == .timedOut {
|
||||
let error = NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "getVersion request timeout",
|
||||
])
|
||||
logger.error("getVersion: timeout")
|
||||
throw error
|
||||
}
|
||||
|
||||
if let error = resultError {
|
||||
throw error
|
||||
}
|
||||
|
||||
guard let value = result else {
|
||||
throw NSError(domain: "RootHelper", code: -1, userInfo: [
|
||||
NSLocalizedDescriptionKey: "getVersion returned nil",
|
||||
])
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -46,14 +46,12 @@
|
||||
}
|
||||
|
||||
public func listener(_: NSXPCListener, shouldAcceptNewConnection newConnection: NSXPCConnection) -> Bool {
|
||||
let allowedBundleIDs = [AppConfiguration.systemExtensionBundleID]
|
||||
guard XPCConnectionValidator.validateConnection(
|
||||
newConnection,
|
||||
teamID: AppConfiguration.teamID,
|
||||
allowedBundleIDs: allowedBundleIDs
|
||||
) else {
|
||||
let info = XPCConnectionValidator.getConnectionInfo(newConnection)
|
||||
logger.warning("Rejected XPC connection: pid=\(info.pid), bundleID=\(info.bundleID ?? "unknown"), teamID=\(info.teamID ?? "unknown")")
|
||||
let bundleID = AppConfiguration.systemExtensionBundleID
|
||||
let requirement = "identifier \"\(bundleID)\" and anchor apple generic and certificate leaf[subject.OU] = \"\(AppConfiguration.teamID)\""
|
||||
do {
|
||||
try newConnection.setCodeSigningRequirement(requirement)
|
||||
} catch {
|
||||
logger.warning("Rejected XPC connection: \(error.localizedDescription)")
|
||||
return false
|
||||
}
|
||||
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
#if os(macOS)
|
||||
import Foundation
|
||||
import Security
|
||||
|
||||
public struct XPCConnectionInfo {
|
||||
public let pid: pid_t
|
||||
public let bundleID: String?
|
||||
public let teamID: String?
|
||||
}
|
||||
|
||||
public enum XPCConnectionValidator {
|
||||
private static func getSecCode(for connection: NSXPCConnection) -> SecCode? {
|
||||
let pid = connection.processIdentifier
|
||||
var code: SecCode?
|
||||
let attributes = [kSecGuestAttributePid: pid] as CFDictionary
|
||||
guard SecCodeCopyGuestWithAttributes(nil, attributes, [], &code) == errSecSuccess else {
|
||||
return nil
|
||||
}
|
||||
return code
|
||||
}
|
||||
|
||||
private static func getSigningInfo(_ code: SecCode) -> [String: Any]? {
|
||||
var staticCode: SecStaticCode?
|
||||
guard SecCodeCopyStaticCode(code, [], &staticCode) == errSecSuccess,
|
||||
let staticCode
|
||||
else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var info: CFDictionary?
|
||||
guard SecCodeCopySigningInformation(staticCode, [], &info) == errSecSuccess else {
|
||||
return nil
|
||||
}
|
||||
return info as? [String: Any]
|
||||
}
|
||||
|
||||
public static func getConnectionInfo(_ connection: NSXPCConnection) -> XPCConnectionInfo {
|
||||
let pid = connection.processIdentifier
|
||||
|
||||
guard let secCode = getSecCode(for: connection),
|
||||
let signingInfo = getSigningInfo(secCode)
|
||||
else {
|
||||
return XPCConnectionInfo(pid: pid, bundleID: nil, teamID: nil)
|
||||
}
|
||||
|
||||
let bundleID = signingInfo[kSecCodeInfoIdentifier as String] as? String
|
||||
let teamID = signingInfo[kSecCodeInfoTeamIdentifier as String] as? String
|
||||
|
||||
return XPCConnectionInfo(pid: pid, bundleID: bundleID, teamID: teamID)
|
||||
}
|
||||
|
||||
public static func validateConnection(
|
||||
_ connection: NSXPCConnection,
|
||||
teamID: String,
|
||||
allowedBundleIDs: [String]
|
||||
) -> Bool {
|
||||
guard let secCode = getSecCode(for: connection) else {
|
||||
return false
|
||||
}
|
||||
|
||||
let requirement = "anchor apple generic and certificate leaf[subject.OU] = \"\(teamID)\""
|
||||
var secRequirement: SecRequirement?
|
||||
guard SecRequirementCreateWithString(requirement as CFString, [], &secRequirement) == errSecSuccess,
|
||||
let req = secRequirement,
|
||||
SecCodeCheckValidity(secCode, [], req) == errSecSuccess
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let signingInfo = getSigningInfo(secCode),
|
||||
let bundleID = signingInfo[kSecCodeInfoIdentifier as String] as? String,
|
||||
allowedBundleIDs.contains(bundleID)
|
||||
else {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
}
|
||||
#endif
|
||||
Reference in New Issue
Block a user