Using OSX system keychain from a daemon - macos

I have a daemon that needs to run as root and is started by launchd. This daemon needs to store some user supplied credentials so I have it writing them to the System keychain using SecKeychainOpen and similar functions.
I'm pretty sure that since this runs as root I have to use the System keychain (since using a user's login keychain is not correct as this doesn't run as a normal user).
My installer loads this into launchd using launchctl at the end of the install. The problem is that it doesn't actually start until after a reboot. I had OnLOad set as true in the plist, but it appears that when using the system keychain I need to the reboot for it to work.
I was wondering if anyone knows of some way to deal with this since it would be a much better user experience if a reboot was not necessary. So to be clear, can I programatically access the System keychain from a daemon using launchd without a reboot?
Thanks for any advice or ideas.

Since I eventually figured out that my problem wasn't what I thought it was at all, I figure I should put the resolution to it up here.
It turns out (despite what I read on a few sites) that it is perfectly OK to programatically access the System keychain with a daemon using launchd without a reboot. Just load the plist in the normal way (with root permission of course) and it all works.
My issue was the my postinstall script was never being run and was actually never even included in my .pkg installer when building on certain macs. Apparently, if you do not have PackageMaker.app installed, macports will still make a dmg with an installer for you, but the installer is a directory rather than the proper single file and it may lack certain parts (such as my postinstall script).
PackageMaker.app can be found on the Apple Developer site in the Xcode AuxTools package (it then needs to be put in either /Applications or somewhere else macports can find it).

Related

Uninstalling items installed by an .app when user deletes it, including SMJobBless helpers

The short version: is it possible to delete helper tools which were set up by the app (SMJobBless() etc.) when the app is deleted? If so, how?
The long version:
The Mac app we are developing unfortunately requires admin privileges to perform an occasional operation, and it also requires a background task to be live for other apps' plugins to connect to even when the app itself isn't running (this one can be unprivileged). The app will be signed with a Developer ID certificate, and distributed only outside the App Store.
We'd like the app to be a "good citizen" as far as possible, also on uninstall.
For the background task, we're using a login item, created using SMLoginItemSetEnabled(). This isn't amazing, because XPC messaging doesn't seem to work (we're using CFMessagePort instead - alternative suggestions welcome), but if the user deletes the app, the login item at least doesn't get loaded anymore on next login. I suspect there's still a trace of it somewhere in the system, but the executable inside the .app bundle is used, and when that disappears, the login item no longer runs.
For the occasional operation requiring admin rights, we've got a privileged helper tool which our app installs using SMJobBless(), and which implements a named XPC service, so the task spins up on demand when it receives a message from the main app. This is what Apple recommends and describes in its Even Better Authorization Sample.
The helper executable is copied to /Library/PrivilegedHelperTools/ by SMJobBless(), and the embedded launchd.plist ends up in /Library/LaunchDaemons/. Even though the OS has the information on which app "owns" the helper, it doesn't seem to uninstall it when the user deletes the app. Apple's sample is silent on uninstalling, other than the uninstall.sh script which is apparently intended to be used during development only. We don't need this helper while the app isn't running, so installing it as a full-blown launch daemon is slightly overkill, but we'd also like to avoid repeatedly annoying the user with the password prompt too. Besides, Apple advises against other forms of running code with admin privileges than SMJobBless() these days - for example SMJobSubmit() is marked deprecated.
So how do we clean up after ourselves?
I've found SMJobRemove(), but (a) when would we call that in our case - you can't run code on .app bundle deletion, or can you? and (b) it doesn't actually seem to clean up.
The only 2 things I can think of are not terribly satisfying:
Some kind of uninstaller app or script. But that seems pretty ugly too.
Don't worry about it and just leave a mess behind when the user deletes our app.
Update:
There have been some changes in this area with macOS 13.0 Ventura; there's an introduction to the new mechanism in the WWDC22 session 'What’s new in privacy'. The new SMAppService APIs support automatic cleanup for daemons, agents and login items. Unfortunately you'll of course still have to find a workaround for any older macOS versions you support.
Original answer:
There has been a similar question on the Apple Developer Forums at https://forums.developer.apple.com/thread/66821 - the recommendation by Apple is a manual uninstall mechanism, and consuming as few resources as possible if the user does not do this.
Apple DTS staff further recommended implementing a self-uninstall mechanism in the privileged launch daemon, to be triggered from the app via XPC. This is what we're going with.
I think the only solution you have right now is to use the uninstall shell code that you mentioned in order to physically remove the privileged helper from disk or to build an uninstaller for it. Either way you will have to ask the user to enter his/her password. This what all installers / uninstallers that require privileged access to the system do, and for a very good reason. That's why I avoid like the plague to use privileged helpers, but I understand that sometimes you really have to. I don't think it is good that you leave such a helper in the user's system, because it will reload next time the user starts up the computer.
I just checked ServiceManagement.h header and they state that SMJobRemove will be replaced by an API that will be made available through libxpc in the future. (Sometimes you really need to go to the headers to get extra info that the documentation does not give you.) Hopefully this promised replacement will uninstall it for us. However, I'd file a bug report and ask for that enhancement.
One solution you could consider is to include an uninstaller script or program in your .app bundle.
You can then pass the path of this small tool to your helper tool (via IPC) and have the execute the the uninstaller, thereby deleting itself. You will have to be careful that components are removed in the right order but it can be made to work.
You're correct that Apple does not provide an API to uninstall a helper tool installed with SMJobBless nor do they do so automatically. As for why macOS doesn't automatically do an uninstall, my educated guess is because macOS fundamentally doesn't have a unified concept of "install". While it's convention for apps to be located in /Applications (and a few other locations), it's perfectly valid for apps to be located and run from anywhere on the system including external drives and network drives. For example should macOS uninstall helper tools when apps disappear because the drive they're on is disconnected?
In terms of how to uninstall, doing so requires root permission and so realistically have the helper tool itself do the uninstall is the easiest option. You can have your app via XPC tell the helper to uninstall itself. Here's an example in Swift of how to do this; it's part of SwiftAuthorizationSample. The basic idea is:
Use the launchctl command line tool to unload the helper tool
Delete the helper tool executable
Delete the helper tool launchd plist
But there's a bit of additional complexity involved because launchctl won't let you unload a running process.

Where to store services that shouldn't be moved (OSX app install)?

I am building a desktop app which creates a service (daemon) during install.
Before registering the service, though, I want to install the files of this service to a location, different than the app's Contents dir - the user might want to move the app from the Applications dir, and if the service changes location on the next start of the OS, it will not be located and my app will not be able to find it.
Where should I install the service at?
Thanks in advance.
Update:
Is "~/Library/Services" the right place?
Or, perhaps, "/Drivename/Library/Services" for multi-user use?
There's a terminology ambiguity with this question and it might be causing you some difficulty with determining where to locate your files.
Under OSX, a Service can have two meanings, one of which is relating to code that executes in response to the Services menu item in applications, and that's what is stored in the various /Library/Services, /System/Library/Services, and ~/Library/Services directories, so you don't want to place them there.
Daemon Control file placement
Assuming about a daemon (which you mention specifically in the text of your message), then you are going to want to place it in one of the LaunchAgent or LaunchDaemon directories and use launchd to start the daemon for you. For launchd purposes, Agents are run per user and Daemons are run per system (difference is how many copies are run).
For daemons/services that are used by a single user and started when the user logs in, their configuration files may be stored in ~/Library/LaunchAgents, which will allow the system to kick them off whenever the user logs in to the system. If you need something that will be available at all times (i.e. starts when the system boots), you'll need to gain privileges and install in the /Library/LaunchDaemons location.
That's the tale of the configuration files for launchd, but it doesn't address directly your question of where to put the executables to prevent them from getting disconnected if the user relocates the Application outside of the /Applications folder.
Daemon executable placement
These days, fewer users are moving apps out of the /Applications folder unless they're intentionally uninstalling or deactivating them, so you may want to avoid trying to prevent this. Instead, when the application launches, you should verify that the daemon is running and if it isn't, install and activate the appropriate launchd configurations using your application's internal Library folder as the repository for the code which is referenced by the launchd configuration.
If you choose to store them separately, you should place them in either ~/Library/Application Support/(your app's id) or /Library/Application Support/(your app's id), depending on whether you are installing per-user or per-machine. Generally, the latter is preferred.

How to distribute an OSX program with the suid bit set through a DMG?

I have a small diagnostic OSX program - a small menu-tray app - that I need to get to a customer. The program makes use of dtrace. As such, it has an auxiliary executable in it's MacOS directory with its suid bit set and ownership set to root:wheel. The helper's only role is to immediately exec() to dtrace with an included dtrace script.
My problem is that I can't figure out a good way to get this to the customer. Naively putting it into a .zip archive wiped out all the special permissions.
When I tried to put it in a DMG, the root:wheel ownership got reset to mine (ted:staff). I found that I was supposed to use diskutil to enableOwnership on the DMG to get it to start respecting file object ownership settings. So now I can see that inside the DMG, my helper program has the required root:wheel and +s suid permissions set.
Now, however, I've found that when I drag and drop the app bundle out of the DMG and onto the desktop, the permissions get reset again. Moreover, when I try to run the application from the DMG, it behaves as though the helper program doesn't have the suid bit set.
Stepping back from this a bit, I don't see why this should work at all. It seems like it would be a nasty and really obvious security hole to allow developers to distribute app bundles with the suid bit set like this.
So how is this done at all? Do I need a package installer? Will that preserve the suid bit? Can someone school me on how this is supposed to work?
You are correct that (if there were a simple way to do this) it would be a nasty security hole. You should never be able to create/install a setuid root executable without going through some admin authorization step. Indeed, you shouldn't even be able to create/install a file with ownership set to any user other than your own.
(Actually, there is an exception to that in 10.9: the App Store will allow you to install apps without admin rights. But that's considered relatively safe because Apple vets the apps before they're allowed in the store.)
The "right" way to do this is with a package installer. It can be set to request admin authorization for the installation, and use that to install files with whatever ownership and permissions you want (including the setuid bit).
Perhaps using a .tar file would work. They are very good for keeping all of the UNIX specific baggage like ownership attached to folders and files inside of them.

Sandboxed Mac app does not always launch automatically using SMLoginItemSetEnabled()

I have a very strange issue with a sandboxed Mac app I'm developing. One requirement is that the user should have the possibility to launch the app when the system starts. For this, I'm using SMLoginItemSetEnabled() as described on http://blog.timschroeder.net/2012/07/03/the-launch-at-login-sandbox-project/.
When the user starts the app for the first time and enables this option, I can see an entry is being added to launchctl by using launchctl list. When I reboot the system, the app is not being started. More strange is the fact that the entry found using launchctl list has disappeared. However, a similar entry is still available in /private/var/db/launchd.db/com.apple.launchd.peruser.501/overrides.plist with key Disabled being false.
When I start the app manually and again set the option to start automatically, the entry is again available in launchctl list. When I reboot the system the app is being launched automatically. Concluding, for some reason SMLoginItemSetEnabled() only works the second time I run the app. Therefor it looks similar to this issue: https://stackoverflow.com/questions/16354295/sandbox-app-with-loginitems-only-work-after-second-app-launch. However, no solution is provided.
https://stackoverflow.com/questions/16354295/sandbox-app-with-loginitems-only-work-after-second-app-launch
If you're like me, you probably had extra copies (generated by Xcode, etc) laying around that seem to confuse LaunchServices.
I wrote a post about it here: Login Items in macOS 10.11 and newer
But the short version is, use lsregister -dump to find all copies that LaunchServices knows about, remove them, then use lsregister -kill to reset the LaunchServices database when you're done.

What does the console error 'Unable to clear quarantine' mean?

I'm making an OS X Installer package. I download it from a build machine onto a test machine (10.5.8) and then run it. In the console, the following message is immediately logged:
kernel[0]: Finder[52646] Unable to clear quarantine '<package name>': 30
From what I've read, Leopard sets a quarantine extended attribute on all downloaded files. It's possible to use xattr to remove that flag, although that obviously doesn't apply to this situation, since a program can't un-quarantine itself.
I don't have a problem with the quarantine itself, or with Leopard popping up some dialog asking whether the user trusts the program. But I would like to know why the quarantine could not be cleared, and whether there is a way to modify my installer or the scripts it runs to prevent that error from being logged.
Does the user on the test machine have administrative privileges? If the app is in a folder such as /Applications, you'll need them. If you can not change the user, try to download the app in his home folder.
I'm guessing you downloaded it as a privileged user but you're running the installer as another user. Since the file is owned by the user that downloaded it, the user that wants to run it can only do so read-only. Check the file ownership and permissions to see if i'm right.
Try to copy from your build server using ditto (and pay attention on various switches that control the copy src and dst)
check access rights on the share of your build server

Resources