From d9c645d4141e242995620ccda4e1c698f58552c1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rafa=C5=82=20Cie=C5=9Blak?= Date: Mon, 29 Jul 2024 16:09:08 +0200 Subject: [PATCH] VNet daemon: Set code signing requirements (#44639) * Move Go error definitions to common_darwin.go Both client_darwin.go and serivce_darwin.go are going to use them, so it doesn't make sense to keep them in either of those files or scattered across them. * Roll protocol_darwin into common_darwin There's little need for those two files to be kept separate. * Add function for getting code signing requirement Both the client and the daemon are going to set a requirement towards each other, hence why this function is in common. * Set code signing requirement towards daemon client * Set code signing requirement towards daemon service * Add info about mdfind to README * Keep #cgo declarations in a single file * Fix typos * Move const to where it's used --- build.assets/macos/tshdev/README.md | 9 ++++ lib/vnet/daemon/client_darwin.go | 35 ++++++++----- lib/vnet/daemon/client_darwin.h | 10 ++-- lib/vnet/daemon/client_darwin.m | 25 ++++++++-- lib/vnet/daemon/common_darwin.go | 57 +++++++++++++++++++++ lib/vnet/daemon/common_darwin.h | 40 +++++++++++++++ lib/vnet/daemon/common_darwin.m | 77 +++++++++++++++++++++++++++++ lib/vnet/daemon/protocol_darwin.h | 31 ------------ lib/vnet/daemon/protocol_darwin.m | 22 --------- lib/vnet/daemon/service_darwin.go | 20 ++++++-- lib/vnet/daemon/service_darwin.h | 18 ++++++- lib/vnet/daemon/service_darwin.m | 30 ++++++++--- 12 files changed, 292 insertions(+), 82 deletions(-) create mode 100644 lib/vnet/daemon/common_darwin.go delete mode 100644 lib/vnet/daemon/protocol_darwin.h delete mode 100644 lib/vnet/daemon/protocol_darwin.m diff --git a/build.assets/macos/tshdev/README.md b/build.assets/macos/tshdev/README.md index 67c2060ba4d20..c2b82452c3726 100644 --- a/build.assets/macos/tshdev/README.md +++ b/build.assets/macos/tshdev/README.md @@ -181,6 +181,15 @@ launch the daemon with the following error: After resetting the db and restarting the device, everything seemed to be working again. +In theory, it's possible to list all app bundles with a certain bundle identifier by running the +following command: + +``` +mdfind kMDItemCFBundleIdentifier = "com.goteleport.tshdev" +``` + +In practice, getting rid of all but one bundle didn't appear to solve the problem. + ### Daemon does not start List all jobs loaded into launchd. The second column is the status which you can then inspect. diff --git a/lib/vnet/daemon/client_darwin.go b/lib/vnet/daemon/client_darwin.go index 3aa1e7320ad21..9c53d8a7ee580 100644 --- a/lib/vnet/daemon/client_darwin.go +++ b/lib/vnet/daemon/client_darwin.go @@ -19,11 +19,9 @@ package daemon -// #cgo CFLAGS: -Wall -xobjective-c -fblocks -fobjc-arc -mmacosx-version-min=10.15 -// #cgo LDFLAGS: -framework Foundation -framework ServiceManagement // #include // #include "client_darwin.h" -// #include "protocol_darwin.h" +// #include "common_darwin.h" import "C" import ( @@ -304,6 +302,28 @@ func startByCalling(ctx context.Context, bundlePath string, config Config) error return } + if errorDomain == nsCocoaErrorDomain && errorCode == errorCodeNSXPCConnectionInterrupted { + const clientNSXPCConnectionInterruptedDebugMsg = "The connection was interrupted when trying to " + + "reach the XPC service. If there's no clear error logs on the daemon side, it might mean that " + + "the client does not satisfy the code signing requirement enforced by the daemon. " + + "Start capturing logs in Console.app and repeat the scenario. Look for " + + "\"xpc_support_check_token: error: status: -67050\" in the logs to verify " + + "that the connection was interrupted due to the code signing requirement." + log.DebugContext(ctx, clientNSXPCConnectionInterruptedDebugMsg) + errC <- trace.Wrap(errXPCConnectionInterrupted) + return + } + + if errorDomain == vnetErrorDomain && errorCode == errorCodeMissingCodeSigningIdentifiers { + errC <- trace.Wrap(errMissingCodeSigningIdentifiers) + return + } + + if errorDomain == nsCocoaErrorDomain && errorCode == errorCodeNSXPCConnectionCodeSigningRequirementFailure { + errC <- trace.Wrap(errXPCConnectionCodeSigningRequirementFailure, "the daemon does not appear to be code signed correctly") + return + } + errC <- trace.Errorf("could not start VNet daemon: %v", C.GoString(res.error_description)) return } @@ -319,15 +339,6 @@ func startByCalling(ctx context.Context, bundlePath string, config Config) error } } -var ( - // vnetErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet. - vnetErrorDomain = C.GoString(C.VNEErrorDomain) - // errorCodeAlreadyRunning is returned within [vnetErrorDomain] errors to indicate that the daemon - // received a message to start after it was already running. - errorCodeAlreadyRunning = int(C.VNEAlreadyRunningError) - errAlreadyRunning = errors.New("VNet is already running") -) - func sleepOrDone(ctx context.Context, d time.Duration) error { timer := time.NewTimer(d) defer timer.Stop() diff --git a/lib/vnet/daemon/client_darwin.h b/lib/vnet/daemon/client_darwin.h index a7f5e4c8bdb1c..e362c4add527b 100644 --- a/lib/vnet/daemon/client_darwin.h +++ b/lib/vnet/daemon/client_darwin.h @@ -2,7 +2,6 @@ #define TELEPORT_LIB_VNET_DAEMON_CLIENT_DARWIN_H_ #include "common_darwin.h" -#include "protocol_darwin.h" #import @@ -44,10 +43,15 @@ typedef struct StartVnetRequest { typedef struct StartVnetResult { bool ok; + // error_domain is either VNEErrorDomain, NSOSStatusErrorDomain, or NSCocoaErrorDomain. const char *error_domain; - // error_code is code taken from an NSError instance encountered during the call to StartVnet. - // If ok is false, error_code is greater than zero and identifies the reason behind the error. + // If error_domain is set to VNEErrorDomain, error_code is one of the VNE codes from common_darwin.h. + // If error_domain is NSOSStatusErrorDomain, error_code comes from OSStatus of Code Signing framework. + // https://developer.apple.com/documentation/security/1574088-code_signing_services_result_cod?language=objc + // If error_domain is NSCocoaErrorDomain, it's likely to be about XPC. It's best to inspect it + // on https://osstatus.com in that case. int error_code; + // error_description includes the full representation of the error, including domain and code. const char *error_description; } StartVnetResult; diff --git a/lib/vnet/daemon/client_darwin.m b/lib/vnet/daemon/client_darwin.m index d2fd9256ead73..b131926d04038 100644 --- a/lib/vnet/daemon/client_darwin.m +++ b/lib/vnet/daemon/client_darwin.m @@ -19,7 +19,6 @@ #include "client_darwin.h" #include "common_darwin.h" -#include "protocol_darwin.h" #import #import @@ -78,15 +77,17 @@ @interface VNEDaemonClient () @property(nonatomic, strong, readwrite) NSXPCConnection *connection; @property(nonatomic, strong, readonly) NSString *bundlePath; +@property(nonatomic, strong, readonly) NSString *codeSigningRequirement; @end @implementation VNEDaemonClient -- (id)initWithBundlePath:(NSString *)bundlePath { +- (id)initWithBundlePath:(NSString *)bundlePath codeSigningRequirement:(NSString *)codeSigningRequirement { self = [super init]; if (self) { _bundlePath = bundlePath; + _codeSigningRequirement = codeSigningRequirement; } return self; } @@ -102,6 +103,12 @@ - (NSXPCConnection *)connection { self->_connection = nil; }; + // The daemon won't even be started on macOS < 13.0, so we don't have to handle the else branch + // of this condition. + if (@available(macOS 13, *)) { + [_connection setCodeSigningRequirement:_codeSigningRequirement]; + } + // New connections always start in a suspended state. [_connection resume]; } @@ -134,7 +141,18 @@ - (void)invalidate { void StartVnet(StartVnetRequest *request, StartVnetResult *outResult) { if (!daemonClient) { - daemonClient = [[VNEDaemonClient alloc] initWithBundlePath:@(request->bundle_path)]; + NSString *requirement = nil; + NSError *error = nil; + bool ok = getCodeSigningRequirement(&requirement, &error); + if (!ok) { + outResult->ok = false; + outResult->error_domain = VNECopyNSString([error domain]); + outResult->error_code = (int)[error code]; + outResult->error_description = VNECopyNSString([error description]); + return; + } + + daemonClient = [[VNEDaemonClient alloc] initWithBundlePath:@(request->bundle_path) codeSigningRequirement:requirement]; } dispatch_semaphore_t sema = dispatch_semaphore_create(0); @@ -142,6 +160,7 @@ void StartVnet(StartVnetRequest *request, StartVnetResult *outResult) { [daemonClient startVnet:request->vnet_config completion:^(NSError *error) { if (error) { + outResult->ok = false; outResult->error_domain = VNECopyNSString([error domain]); outResult->error_code = (int)[error code]; outResult->error_description = VNECopyNSString([error description]); diff --git a/lib/vnet/daemon/common_darwin.go b/lib/vnet/daemon/common_darwin.go new file mode 100644 index 0000000000000..1daae730aa9a3 --- /dev/null +++ b/lib/vnet/daemon/common_darwin.go @@ -0,0 +1,57 @@ +// Teleport +// Copyright (C) 2024 Gravitational, Inc. +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see . + +//go:build vnetdaemon +// +build vnetdaemon + +package daemon + +// #cgo CFLAGS: -Wall -xobjective-c -fblocks -fobjc-arc -mmacosx-version-min=10.15 +// #cgo LDFLAGS: -framework Foundation -framework ServiceManagement +// #include "common_darwin.h" +import "C" + +import ( + "errors" +) + +var ( + // vnetErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet. + vnetErrorDomain = C.GoString(C.VNEErrorDomain) + + // errorCodeAlreadyRunning is returned within [vnetErrorDomain] errors to indicate that the daemon + // received a message to start after it was already running. + errorCodeAlreadyRunning = int(C.VNEAlreadyRunningError) + errAlreadyRunning = errors.New("VNet is already running") + + // errorCodeMissingCodeSigningIdentifiers is returned within [vnetErrorDomain] Obj-C errors and + // transformed to [errMissingCodeSigningIdentifiers] in Go. + errorCodeMissingCodeSigningIdentifiers = int(C.VNEMissingCodeSigningIdentifiersError) + errMissingCodeSigningIdentifiers = errors.New("either identifier or team identifier is missing in code signing information; is the binary signed?") +) + +var ( + // nsCocoaErrorDomain is a generic error domain used in a lot of Apple's Cocoa frameworks. + nsCocoaErrorDomain = "NSCocoaErrorDomain" + + // https://developer.apple.com/documentation/foundation/1448136-nserror_codes/nsxpcconnectioninterrupted?changes=latest_major&language=objc + errorCodeNSXPCConnectionInterrupted = int(C.NSXPCConnectionInterrupted) + errXPCConnectionInterrupted = errors.New("XPC connection interrupted") + + // https://developer.apple.com/documentation/foundation/1448136-nserror_codes/nsxpcconnectioncodesigningrequirementfailure?language=objc + errorCodeNSXPCConnectionCodeSigningRequirementFailure = int(C.NSXPCConnectionCodeSigningRequirementFailure) + errXPCConnectionCodeSigningRequirementFailure = errors.New("code signing requirement failed") +) diff --git a/lib/vnet/daemon/common_darwin.h b/lib/vnet/daemon/common_darwin.h index f549ca1b8ef79..92c73b310f4b3 100644 --- a/lib/vnet/daemon/common_darwin.h +++ b/lib/vnet/daemon/common_darwin.h @@ -3,6 +3,35 @@ #import +// VNEErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet. +extern const char* const VNEErrorDomain; + +// VNEAlreadyRunningError indicates that the daemon already received a VNet config. +// It won't accept a new one during its lifetime, instead it's expected to stop, after +// which the client might spawn a new instance of the daemon. +extern const int VNEAlreadyRunningError; +// VNEMissingCodeSigningIdentifiersError indicates that either the identifier or the team identifier are missing. +// This can happen if the binary is unsigned, see the docs for SecCodeCopySigningInformation. +// https://developer.apple.com/documentation/security/1395809-seccodecopysigninginformation?language=objc +extern const int VNEMissingCodeSigningIdentifiersError; + +typedef struct VnetConfig { + const char *socket_path; + const char *ipv6_prefix; + const char *dns_addr; + const char *home_path; +} VnetConfig; + +@protocol VNEDaemonProtocol +// startVnet passes the config back to Go code (which then starts VNet in a separate thread) +// and returns immediately. +// +// Only the first call to this method starts VNet. Subsequent calls return VNEAlreadyRunningError. +// The daemon process exits after VNet is stopped, after which it can be spawned again by calling +// this method. +- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))completion; +@end + // Returns the label for the daemon by getting the identifier of the bundle // this executable is shipped in and appending ".vnetd" to it. // @@ -16,4 +45,15 @@ NSString *DaemonLabel(NSString *bundlePath); // The caller is expected to free the returned pointer. const char *VNECopyNSString(NSString *val); +// getCodeSigningRequirement calculates the requirement that will be matched against +// the designated requirement of the app on the other side of an XPC connection. +// It does so based on the code signing information of the current binary, as it assumes that +// both the VNet client and the VNet daemon use the same binary. +// +// On success, it returns true and sets outRequirement. +// On error, it returns false and sets outError. Returns errors of VNEErrorDomain and +// NSOSStatusErrorDomain. Errors with the latter domain are likely to match Code Signing OSStatus values. +// https://developer.apple.com/documentation/security/1574088-code_signing_services_result_cod?language=objc +bool getCodeSigningRequirement(NSString **outRequirement, NSError **outError); + #endif /* TELEPORT_LIB_VNET_DAEMON_COMMON_DARWIN_H_ */ diff --git a/lib/vnet/daemon/common_darwin.m b/lib/vnet/daemon/common_darwin.m index 194ac7f0f0f2e..3a405209893f8 100644 --- a/lib/vnet/daemon/common_darwin.m +++ b/lib/vnet/daemon/common_darwin.m @@ -17,10 +17,17 @@ // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . +#import #import +#import #include +const char* const VNEErrorDomain = "com.Gravitational.Vnet.ErrorDomain"; + +const int VNEAlreadyRunningError = 1; +const int VNEMissingCodeSigningIdentifiersError = 2; + NSString *DaemonLabel(NSString *bundlePath) { NSBundle *main = [NSBundle bundleWithPath:bundlePath]; if (!main) { @@ -41,3 +48,73 @@ } return strdup(""); } + +bool getCodeSigningRequirement(NSString **outRequirement, NSError **outError) { + SecCodeRef codeObj = nil; + OSStatus status = SecCodeCopySelf(kSecCSDefaultFlags, &codeObj); + if (status != errSecSuccess) { + if (outError) { + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; + } + return false; + } + + CFDictionaryRef cfCodeSignInfo = nil; + // kSecCSSigningInformation must be provided as a flag for the team identifier to be included + // in the returned dictionary. + status = SecCodeCopySigningInformation(codeObj, kSecCSSigningInformation, &cfCodeSignInfo); + // codeObj is no longer needed. Manually release it, as we own it since we got it from a function + // with "Copy" in its name. + // https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFMemoryMgmt/Concepts/Ownership.html#//apple_ref/doc/writerid/cfCreateRule + CFRelease(codeObj); + if (status != errSecSuccess) { + if (outError) { + *outError = [NSError errorWithDomain:NSOSStatusErrorDomain code:status userInfo:nil]; + } + return false; + } + + // Transfer ownership of cfCodeSignInfo to Obj-C, which means we don't have to CFRelease it manually. + // We can transfer the ownership of cfCodeSignInfo because we own it (we got it from a function + // with "Copy" in its name). + // https://developer.apple.com/documentation/foundation/1587932-cfbridgingrelease + NSDictionary *codeSignInfo = (NSDictionary *)CFBridgingRelease(cfCodeSignInfo); + // We don't own kSecCodeInfoIdentifier, so we cannot call CFBridgingRelease on it. + // __bridge transfers a pointer between Obj-C and CoreFoundation with no transfer of ownership. + // Values extracted out of codeSignInfo are cast to toll-free bridged Obj-C types. + // https://developer.apple.com/library/archive/documentation/CoreFoundation/Conceptual/CFDesignConcepts/Articles/tollFreeBridgedTypes.html#//apple_ref/doc/uid/TP40010677-SW2 + // https://stackoverflow.com/questions/18067108/when-should-you-use-bridge-vs-cfbridgingrelease-cfbridgingretain + NSString *identifier = codeSignInfo[(__bridge NSString *)kSecCodeInfoIdentifier]; + NSString *teamIdentifier = codeSignInfo[(__bridge NSString *)kSecCodeInfoTeamIdentifier]; + + if (!identifier || [identifier length] == 0 || !teamIdentifier || [teamIdentifier length] == 0) { + if (outError) { + *outError = [NSError errorWithDomain:@(VNEErrorDomain) code:VNEMissingCodeSigningIdentifiersError userInfo:nil]; + } + return false; + } + + // The requirement will be matched against the designated requirement of the application on + // the other side of an XPC connection. It is based on the designated requirement of tsh.app. + // To inspect the designated requirement of an app, use the following command: + // + // codesign --display -r - + // + // Breakdown of individual parts of the requirement: + // * `identifier "foo"` is satisfied if the code signing identifier matches the provided one. + // It is not the same as the bundle identifier. + // * `anchor apple generic` is satisfied by any code signed with any code signing identity issued + // by Apple. + // * `certificate leaf[field(bunch of specific numbers)]` is satisfied by code signed with + // Developer ID Application certs. + // * `certificate leaf[subject.OU]` is satisfied by certs with a specific Team ID. + // + // Read more at: + // https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements#Designated-requirement + // https://developer.apple.com/documentation/technotes/tn3127-inside-code-signing-requirements#Xcode-designated-requirement-for-Developer-ID-code + if (outRequirement) { + *outRequirement = [NSString stringWithFormat:@"identifier \"%@\" and anchor apple generic and certificate leaf[field.1.2.840.113635.100.6.1.13] and certificate leaf[subject.OU] = %@", identifier, teamIdentifier]; + } + + return true; +} diff --git a/lib/vnet/daemon/protocol_darwin.h b/lib/vnet/daemon/protocol_darwin.h deleted file mode 100644 index d150bb99d37a4..0000000000000 --- a/lib/vnet/daemon/protocol_darwin.h +++ /dev/null @@ -1,31 +0,0 @@ -#ifndef TELEPORT_LIB_VNET_DAEMON_PROTOCOL_DARWIN_H_ -#define TELEPORT_LIB_VNET_DAEMON_PROTOCOL_DARWIN_H_ - -#import - -// VNEErrorDomain is a custom error domain used for Objective-C errors that pertain to VNet. -extern const char* const VNEErrorDomain; - -// VNEAlreadyRunningError indicates that the daemon already received a VNet config. -// It won't accept a new one during its lifetime, instead it's expected to stop, after -// which the client might spawn a new instance of the daemon. -extern const int VNEAlreadyRunningError; - -typedef struct VnetConfig { - const char *socket_path; - const char *ipv6_prefix; - const char *dns_addr; - const char *home_path; -} VnetConfig; - -@protocol VNEDaemonProtocol -// startVnet passes the config back to Go code (which then starts VNet in a separate thread) -// and returns immediately. -// -// Only the first call to this method starts VNet. Subsequent calls return VNEAlreadyRunningError. -// The daemon process exits after VNet is stopped, after which it can be spawned again by calling -// this method. -- (void)startVnet:(VnetConfig *)vnetConfig completion:(void (^)(NSError *error))completion; -@end - -#endif /* TELEPORT_LIB_VNET_DAEMON_PROTOCOL_DARWIN_H_ */ diff --git a/lib/vnet/daemon/protocol_darwin.m b/lib/vnet/daemon/protocol_darwin.m deleted file mode 100644 index f00e4948ef0c1..0000000000000 --- a/lib/vnet/daemon/protocol_darwin.m +++ /dev/null @@ -1,22 +0,0 @@ -//go:build vnetdaemon -// +build vnetdaemon - -// Teleport -// Copyright (C) 2024 Gravitational, Inc. -// -// This program is free software: you can redistribute it and/or modify -// it under the terms of the GNU Affero General Public License as published by -// the Free Software Foundation, either version 3 of the License, or -// (at your option) any later version. -// -// This program is distributed in the hope that it will be useful, -// but WITHOUT ANY WARRANTY; without even the implied warranty of -// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -// GNU Affero General Public License for more details. -// -// You should have received a copy of the GNU Affero General Public License -// along with this program. If not, see . - -const char* const VNEErrorDomain = "com.Gravitational.Vnet.ErrorDomain"; - -const int VNEAlreadyRunningError = 1; diff --git a/lib/vnet/daemon/service_darwin.go b/lib/vnet/daemon/service_darwin.go index cf996097efe3f..bd7db4096da06 100644 --- a/lib/vnet/daemon/service_darwin.go +++ b/lib/vnet/daemon/service_darwin.go @@ -19,8 +19,6 @@ package daemon -// #cgo CFLAGS: -Wall -xobjective-c -fblocks -fobjc-arc -mmacosx-version-min=10.15 -// #cgo LDFLAGS: -framework Foundation // #include // #include "service_darwin.h" import "C" @@ -47,7 +45,23 @@ func Start(ctx context.Context, workFn func(context.Context, Config) error) erro cBundlePath := C.CString(bundlePath) defer C.free(unsafe.Pointer(cBundlePath)) - C.DaemonStart(cBundlePath) + var result C.DaemonStartResult + defer func() { + C.free(unsafe.Pointer(result.error_domain)) + C.free(unsafe.Pointer(result.error_description)) + }() + C.DaemonStart(cBundlePath, &result) + if !result.ok { + errorDomain := C.GoString(result.error_domain) + errorCode := int(result.error_code) + + if errorDomain == vnetErrorDomain && errorCode == errorCodeMissingCodeSigningIdentifiers { + return trace.Wrap(errMissingCodeSigningIdentifiers) + } + + return trace.Errorf("could not start daemon: %s", C.GoString(result.error_description)) + } + defer func() { log.InfoContext(ctx, "Stopping daemon") C.DaemonStop() diff --git a/lib/vnet/daemon/service_darwin.h b/lib/vnet/daemon/service_darwin.h index 779c33585b8a4..fd39979eed25f 100644 --- a/lib/vnet/daemon/service_darwin.h +++ b/lib/vnet/daemon/service_darwin.h @@ -5,7 +5,7 @@ @interface VNEDaemonService : NSObject -- (id)initWithBundlePath:(NSString *)bundlePath; +- (id)initWithBundlePath:(NSString *)bundlePath codeSigningRequirement:(NSString *)codeSigningRequirement; // start begins listening for incoming XPC connections. - (void)start; @@ -19,9 +19,23 @@ @end +typedef struct DaemonStartResult { + bool ok; + // error_domain is set to either VNEErrorDomain or NSOSStatusErrorDomain if ok is false. + const char *error_domain; + // If error_domain is set to VNEErrorDomain, error_code is one of the VNE codes from common_darwin.h. + // If error_domain is NSOSStatusErrorDomain, error_code comes from OSStatus of Code Signing framework. + // https://developer.apple.com/documentation/security/1574088-code_signing_services_result_cod?language=objc + int error_code; + // error_description includes the full representation of the error, including domain and code. + const char *error_description; +} DaemonStartResult; + // DaemonStart initializes the XPC service and starts listening for new connections. // It's expected to be called only once, noop if the daemon was already started. -void DaemonStart(const char *bundle_path); +// It might fail if it runs into problems with Code Signing APIs while calucating the code signing +// requirement. In such case, outResult.ok is set to false and the error fields are populated. +void DaemonStart(const char *bundle_path, DaemonStartResult *outResult); // DaemonStop stops the XPC service. Noop if DaemonStart wasn't called. void DaemonStop(void); diff --git a/lib/vnet/daemon/service_darwin.m b/lib/vnet/daemon/service_darwin.m index 8258254eaf249..01b636f72fe6a 100644 --- a/lib/vnet/daemon/service_darwin.m +++ b/lib/vnet/daemon/service_darwin.m @@ -18,7 +18,6 @@ // along with this program. If not, see . #include "common_darwin.h" -#include "protocol_darwin.h" #include "service_darwin.h" #import @@ -44,14 +43,19 @@ @interface VNEDaemonService () @implementation VNEDaemonService -- (id)initWithBundlePath:(NSString *)bundlePath { +- (id)initWithBundlePath:(NSString *)bundlePath codeSigningRequirement:(NSString *)codeSigningRequirement { self = [super init]; if (self) { - // Launch daemons must configure their listener with the machServiceName - // initializer. + // Launch daemons must configure their listener with the machServiceName initializer. _listener = [[NSXPCListener alloc] initWithMachServiceName:DaemonLabel(bundlePath)]; _listener.delegate = self; + // The daemon won't even be started on macOS < 13.0, so we don't have to handle the else branch + // of this condition. + if (@available(macOS 13, *)) { + [_listener setConnectionCodeSigningRequirement:codeSigningRequirement]; + } + _started = NO; _gotVnetConfigSema = dispatch_semaphore_create(0); } @@ -126,12 +130,26 @@ - (BOOL)listener:(NSXPCListener *)listener static VNEDaemonService *daemonService = NULL; -void DaemonStart(const char *bundle_path) { +void DaemonStart(const char *bundle_path, DaemonStartResult *outResult) { if (daemonService) { + outResult->ok = true; + return; + } + + NSString *requirement = nil; + NSError *error = nil; + bool ok = getCodeSigningRequirement(&requirement, &error); + if (!ok) { + outResult->ok = false; + outResult->error_domain = VNECopyNSString([error domain]); + outResult->error_code = (int)[error code]; + outResult->error_description = VNECopyNSString([error description]); return; } - daemonService = [[VNEDaemonService alloc] initWithBundlePath:@(bundle_path)]; + + daemonService = [[VNEDaemonService alloc] initWithBundlePath:@(bundle_path) codeSigningRequirement:requirement]; [daemonService start]; + outResult->ok = true; } void DaemonStop(void) {