Cannot access keychain item after SMJobBless update - objective-c

We have a problem with updating a helper tool with SMJobBless that has been puzzling us for days now.
We are developing an application for which at some point we need to perform administrative tasks (loading/unloading a kext). We are also using the keychain to store account information for our application.
For the administrative tasks, we use a helper tool that is installed using SMJobBless with which we communicate using DO over Mach ports (with NSConnection).
In the helper tool:
// use our bundle id as our service name
NSString* name = [[NSBundle mainBundle] bundleIdentifier];
launch_data_t checkinRequest = launch_data_new_string(LAUNCH_KEY_CHECKIN);
launch_data_t checkinResponse = launch_msg(checkinRequest);
launch_data_t machServicesDict = launch_data_dict_lookup(checkinResponse, LAUNCH_JOBKEY_MACHSERVICES);
launch_data_t machPort = launch_data_dict_lookup(machServicesDict, [name UTF8String]);
mach_port_t mp = launch_data_get_machport(machPort);
launch_data_free(checkinResponse);
launch_data_free(checkinRequest);
NSMachPort *receivePort = [[NSMachPort alloc] initWithMachPort:mp];
NSConnection *server = [NSConnection connectionWithReceivePort:receivePort sendPort:nil];
In the app:
NSConnection *conn = [NSConnection connectionWithRegisteredName:HELPER_BUNDLE_IDENTIFIER host:nil];
id proxyServerObject = [conn rootProxy];
if(conn && proxyServerObject) {
return [proxyServerObject someMethod];
}
return NO;
We sign both the application and the helper tool using a codesign certificate from Thawte. So far, everything works like a charm. The helper tool is installed and we can communicate with it using DO; our kext is loaded and unloaded successfully.
The problem starts when we try to update our helper tool. We use the info dictionary of the installed tool and the bundled tool in our app bundle to check whether an update of the tool is required and call SMJobBless again to perform the update.
After the SMJobBless call, the following lines appear in the Console:
6/19/12 10:31:24.000 AM kernel: CODE SIGNING: cs_invalid_page(0x104e17000): p=74362[OURAPP] clearing CS_VALID
6/19/12 10:31:24.000 AM kernel: CODE SIGNING: cs_invalid_page(0x10d0de000): p=74364[OURAPPHELPER] clearing CS_VALID
After this, the application is unable to read the application password from our keychain item, the function SecKeychainItemCopyContent returns errSecAuthFailed (-25293). However, no error is reported if we manually verify the code signature of our installed helper tool or application bundle using codesign -vvvv PATH_TO_TOOL_OR_BUNDLE.
The tool and application are signed outside of the Xcode environment and the contents are not altered after the signing process.
We have found one other post that describes a similar situation, but that question is still unanswered.
A related issue might be SMJobBless returning error 4098.
We are testing on OSX 10.7.4.
Anyone faced similar issues or is there something obvious that we are doing wrong?

This is due to a bug related to how SMJobBless replaces the helper tool on disk. In particular, it modifies the binary in place rather than taking the common approach of writing to a temporary file and then renaming it over top of the destination. The effect of this is that if the binary is in memory, the modifications to the file change the memory pages backing the file, invalidating their code signature. I've written up a bug report about this as rdar://problem/13514523. I'd encourage you to file your own if you've not done so already.
A possible workaround may be to have your application ask the helper tool to remove itself from disk before you use SMJobBless to upgrade it. This should result in SMJobBless copying to a new file on disk, bypassing the issue.

Related

How to launch app's FinderSync Extension from code?

I've made finder sync extension using instruction.
It displays in finder when I launch it directly from Xcode. But it is not visible when I run the main app. Is there some way to do it for sandbox app? In not sandboxed app I used the code:
NSTask *fseEnable = [[NSTask alloc] init];
fseEnable.launchPath = #"/usr/bin/pluginkit";
fseEnable.arguments = #[#"-e", #"use", #"-i", #"com.team.AppName.FinderSyncExtension"];
[fseEnable launch];
But it does not work for sandboxed. I tried:
[[NSWorkspace sharedWorkspace] launchAppWithBundleIdentifier:#"com.team.AppName.FinderSyncExtension" options:NSWorkspaceLaunchDefault additionalEventParamDescriptor:nil launchIdentifier:nil];
But It also did not work.
Probably there is some checkbox in project settings to do it. but I haven't fond it.
Also I've found probably sad news in the documentation.
After installing an app extension, a user must take action to enable
it. Often, users can enable an extension within the context of their
current task. If your extension is a Today widget, for example, users
can edit the Today view in Notification Center to enable your
extension. In other cases, users can use Settings (in iOS) or System
Preferences (in macOS) to enable and manage the extensions they
install.
Use int system(const char *) instead of NSTask.
system("pluginkit -e use -i com.team.AppName.FinderSyncExtension");

How can I programmatically determine if my OSX app was launched from the Applications dir?

I've built an app for OSX and built a .dmg installer for it. However, some users have trouble following the HUGE arrow in the background image telling them to drag the app to the /Applications folder. ;-)
I would be nice if it's possible to do the following:
somehow detect if the app was launched from the /Applications dir
if it wasn't, offer to automatically move the app from the dmg to the /Applications dir, for the user, and launch from there
if #2 isn't something that the OS would allow, display an alert and then close down the app.
I can figure out #3 on my own, but I'm wondering if #1 and #2 are possible and how I might go about them.
[NSBundle mainBundle] represents your application bundle. You can use this to get the app's URL.
You can then use NSURL to get the volume information for the app's URL and determine if it was launched from within the DMG. E.g.:
NSURL* bundleUrl = [[NSBundle mainBundle] bundleURL];
NSString* volumeName = nil;
if ([bundleUrl getResourceValue:&volumeName forKey:NSURLVolumeNameKey error:nil])
{
if ([volumeName isEqual:#"My App Installer"]) // or whatever your DMG volume name is
{
[self tryMigratingToAppsFolder];
}
}
Or, you could just check if it's not in /Applications. Keep in mind that some people install apps in different locations.
A full list of NSURL's available volume resources is available here.
Start here https://github.com/potionfactory/LetsMove/ and check out some of the forks if it isn't quite what you want.
Pretty sure there is another library that does this as well but I can't find it right now
For Unix in general, the first argument to the application (argv[0]), is a pointer to the absolute path to the application binary. Check the path for "/Applications". Also, if the app has been launched from the mounted dmg, the path will begin with "/Volumes" if it has been mounted by double-clicking it from the Finder.
You can see the difference using ps:
Launched from the mounted dmg (using Identity Finder.app as an example):
$ ps aux|grep Identity
michael 52525 0.0 0.5 926072 80804 ?? U 12:14PM 0:02.92 /Volumes/Identity Finder/Identity Finder.app/Contents/MacOS/Identity Finder
Launched from /Applications:
$ ps aux|grep Identity
michael 52562 0.0 0.7 976212 116736 ?? S 12:16PM 0:03.24 /Applications/Identity Finder.app/Contents/MacOS/Identity Finder

Using Attach API Outside Of JDK

I have a small application that uses the Attach API to modify some third party classes during runtime. Alas, I have run into a large problem: the Attach API only comes with the JDK. The necessary files I can copy from the JDK and add into my project, but the library responsible for this(attach.(dll|so)) I can't. This is because I would have to copy attach.lib from a resource inside jar, and put it in the JRE/lib directory.
An action that would not work if the user isn't root on a Linux machine, therefore losing compatibility to alot of users (as this app is supposed to run on a server, and most servers are Linux, and I can't be sure all are root)
I looked into all the classes responsible for the attach API (VirtualMachine, AttachProvider etc) but found no place where it is loading the library.
Is it possible to do this? I mean, can I use the Attach API outside of a JDK installation? If so, how?
You can do so by modifying java.library.path:
static void addToLibPath(String path) throws NoSuchFieldException,
SecurityException,
IllegalArgumentException,
IllegalAccessException
{
if (System.getProperty("java.library.path") != null) {
// If java.library.path is not empty, we will prepend our path
// Note that path.separator is ; on Windows and : on Unix-like,
// so we can't hard code it.
System.setProperty("java.library.path",
path + System.getProperty("path.separator")
+ System.getProperty("java.library.path"));
} else {
System.setProperty("java.library.path", path);
}
// Important: java.library.path is cached
// We will be using reflection to clear the cache
Field fieldSysPath = ClassLoader.class.getDeclaredField("sys_paths");
fieldSysPath.setAccessible(true);
fieldSysPath.set(null, null);
}
Call addToLibPath("path") will add "path" to java.library.path.
Please note that java.library.path is cached, and reflection is required to clear the cache.
As far as I know, you need to run the application looking to do the "attach" from within a JDK (not a JRE). By doing this, you don't need to worry about providing the Attach API or its dependencies - as they are all provided for and managed by the JDK. That said, you shouldn't have any "root" concerns with doing this - as you can extract and run/use a JDK as any user (it doesn't have to be installed / executed as "root"). That said, you'll just need to ensure that your program doing the attaching and the program being attached to are running as the same OS user as to not run into security restrictions.
Our experience is that there is no reliable way to use the attach API without a full JDK. This was particularly acute on Windows. You might get it to work, but you might want to look into plain old JMX instead.

Sandboxing, login items and launch App

Trying to make my app works with sandboxing I get stuck in this problem:
I need to add my App to login items so I've followed these steps:
Creating an HelperApp.app (with is agent = YES) that at the open exec this code :
NSString *appPath = #"/Applications/MyNewApp.app";
[[NSWorkspace sharedWorkspace] launchApplication:appPath];
[NSApp terminate:nil];
This app is sandboxed.
I added a copy build phase to my MyNewApp.app bundle with subpath Contents/Library/LoginItems and destination wrapper
And obviously also the MyNewApp.app is sandboxed
In MyNewApp.app I registered with SMLoginItemSetEnabled the bundle id for the HelperApp.app
Now all works correctly but... when MyNewApp.app tries to launch MyNewApp.app I get an error:
LSOpenFromURLSpec() returned -10827 for application /Applications/MyNewApp.app path (null).
But if I try to use the HelperApp.app to launch a not sandboxed (i.e. Steam.app) app it works like a charm!
Is there another way to launch the app? probably NSWorkSpace works with functions not allowed in sandbox context. What do you suggest?
You may want to have a look at my tutorial on how to get launch at login working in the sandbox.

SMJobBless and friends

i made an App that was submitted to the Mac AppStore. For some reasons, they refused it, because it was installing a helper tool using the SMJobBless API.
As this helper tool isn't necessary for most of the App's functionality, i have removed it, and my application got accepted.
So right now, i am packaging a standalone installer for the helper tool that would be downloadable on the internet.
However, after i have installed the files in place, the helper tool refuses to run... The helper tool just has a plist that goes into /Library/LaunchDaemons and a binary that goes into /Library/PrivilegedHelperTools.
Now i'm wondering, what exactly is SMJobBless doing more than moving files into place? Is it registering my tool with launchctl? i tried various things, to manually add it to launchctl, without success: the helper tool just exits after requesting it's checkin request.
The helper tool just contains that:
#autoreleasepool {
launch_data_t req = launch_data_new_string(LAUNCH_KEY_CHECKIN);
launch_data_t resp = launch_msg(req); // AT THIS POINT, RESP IS NULL. Why?
launch_data_t machData = launch_data_dict_lookup(resp, LAUNCH_JOBKEY_MACHSERVICES);
launch_data_t machPData = launch_data_dict_lookup(machData, [kHelperBundleName UTF8String]);
mach_port_t mp = launch_data_get_machport(machPData);
launch_data_free (resp);
launch_data_free (req);
// Prepare connexion.
NSMachPort *rp = [[NSMachPort alloc] initWithMachPort:mp];
PrivilegedHelperProxy
*phProxy = [[[PrivilegedHelperProxy alloc] init] autorelease];
NSConnection *con = [NSConnection connectionWithReceivePort:rp sendPort:nil];
[rp release];
[con setRootObject:phProxy];
[[NSRunLoop currentRunLoop] run];
}
return EXIT_SUCCESS;
This service was running flawlessly when getting installed via the SMJobBless API, and the root proxy was accessible from within my APP...
So what would be the required steps for my installer to get this helper tool properly working, as it would with calls to the SMJobBless API?
Thanks,
Pierre.
Solved. The PList entry for the helper tool wasn't including any ProgramArguments (This didn't seem to be an issue with the SMJobBless API). After adding those to the PList, the helper tool can correctly run, and the installer registers it correctly.
Thanks anyways!
Pierre.