Privileged helper without SMJobBless

To establish a privileged helper daemon from a command line app to handle actions requiring root privileges I still use the old way of SMJobBless. But this is deprecated since OSX 10.13 and I want to finally update it to the new way using SMAppService.

As I'm concerned with securing it against malicious exploits, do you have a recommended up-to-date implementation in Objective-C establishing a privileged helper and verifying it is only used by my signed app?

I've seen the suggestion in the documentation to use SMAppService, but couldn't find a good implementation covering security aspects. My old implementation in brief is as follows:

bool runJobBless () {
	// check if already installed
	NSFileManager* filemgr = [NSFileManager defaultManager];
	if ([filemgr fileExistsAtPath:@"/Library/PrivilegedHelperTools/com.company.Helper"] &&
		[filemgr fileExistsAtPath:@"/Library/LaunchDaemons/com.company.Helper.plist"])
	{
		// check helper version to match the client
		// ...
		return true;
	}
	
	// create authorization reference
	AuthorizationRef authRef;
	OSStatus status = AuthorizationCreate (NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authRef);
	if (status != errAuthorizationSuccess) return false;
	
	// obtain rights to install privileged helper
	AuthorizationItem authItem = { kSMRightBlessPrivilegedHelper, 0, NULL, 0 };
	AuthorizationRights authRights = { 1, &authItem };
	AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagPreAuthorize | kAuthorizationFlagExtendRights;
	status = AuthorizationCopyRights (authRef, &authRights, kAuthorizationEmptyEnvironment, flags, NULL);
	if (status != errAuthorizationSuccess) return false;

	// SMJobBless does it all: verify helper against app and vice-versa, place and load embedded launchd.plist in /Library/LaunchDaemons, place executable in /Library/PrivilegedHelperTools
	CFErrorRef  cfError;
	if (!SMJobBless (kSMDomainSystemLaunchd, (CFStringRef)@"com.company.Helper", authRef, &cfError)) {
		// check helper version to match the client
		// ...
		return true;
	} else {
		CFBridgingRelease (cfError);
		return false;
	}
}

void connectToHelper () {
	// connect to helper via XPC
	NSXPCConnection* c = [[NSXPCConnection alloc] initWithMachServiceName:@"com.company.Helper.mach" options:NSXPCConnectionPrivileged];
	c.remoteObjectInterface = [NSXPCInterface interfaceWithProtocol:@protocol (SilentInstallHelperProtocol)];
	[c resume];

	// call function on helper and wait for completion
	dispatch_semaphore_t semaphore = dispatch_semaphore_create (0);
	[[c remoteObjectProxy] callFunction:^() {
		dispatch_semaphore_signal (semaphore);
	}];
	dispatch_semaphore_wait (semaphore, dispatch_time (DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC));
	dispatch_release (semaphore);
	[c invalidate];
	[c release];
}
Answered by DTS Engineer in 860342022
Activating components embedded in your own bundle means that codesigning validation ensures that your daemon can't be altered.

Right. More specifically, once Gatekeeper has checked your app it becomes subject to app bundle protection. Trusted Execution Resources has a link to the WWDC talk that explains that.

Its not a coincidence that app bundle protection was introduced in macOS 13, which is the very same release that introduced SMAppService.

That means using XPC to validate the connection itself …

For advice on that specifically, see the Validating Signature Of XPC Process link in XPC Resources.

RZillmer, if you’re looking to move away from SMJobBless, you should check out my Getting Started with SMAppService post.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

I've seen the suggestion in the documentation to use SMAppService, but couldn't find a good implementation covering security aspects.

In terms of installation security, this is largely handled by SMAppService "itself". Activating components embedded in your own bundle means that codesigning validation ensures that your daemon can't be altered.

In terms of runtime validation, the techniques for that are basically the same as what's shown in EvenBetterAuthorization. That means using XPC to validate the connection itself and the authorization system to confirm user intent.

__
Kevin Elliott
DTS Engineer, CoreOS/Hardware

Activating components embedded in your own bundle means that codesigning validation ensures that your daemon can't be altered.

Right. More specifically, once Gatekeeper has checked your app it becomes subject to app bundle protection. Trusted Execution Resources has a link to the WWDC talk that explains that.

Its not a coincidence that app bundle protection was introduced in macOS 13, which is the very same release that introduced SMAppService.

That means using XPC to validate the connection itself …

For advice on that specifically, see the Validating Signature Of XPC Process link in XPC Resources.

RZillmer, if you’re looking to move away from SMJobBless, you should check out my Getting Started with SMAppService post.

Share and Enjoy

Quinn “The Eskimo!” @ Developer Technical Support @ Apple
let myEmail = "eskimo" + "1" + "@" + "apple.com"

Thanks a lot for all your help! That should give me enough guidance to do the replacement without losing the security measures.

Privileged helper without SMJobBless
 
 
Q