Fix XPC validation

This commit is contained in:
世界
2026-01-05 15:42:56 +08:00
parent af9cfd0f59
commit 8a62a58ce7
12 changed files with 278 additions and 145 deletions
+6 -8
View File
@@ -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
+48
View File
@@ -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