Escalating privileges on Mac OS X securely, and without using deprecated methods

This week I implemented a much-requested feature in SourceTree for the upcoming 1.3 release (beta 1 went out on Monday, this will make it into beta 2) – a command-line tool so you can quickly pull up SourceTree for the repository you’re in from a terminal. Writing the command-line tool was trivial, but when I came to implement the menu item which would install it in /usr/local/bin, which inherently needs privilege escalation, it turned out to be a lot more complicated than I expected.

How so? Surely lots of people have done this sort of thing before? Well, that’s true, they have – but the problem is that just about all of the existing examples of this use the Authorization Services API call AuthorizationExecuteWithPrivileges – and this method is deprecated in OS X 10.7 (Lion). Now, of course that doesn’t stop you using it (yet), provided you’re willing to turn off the warning that building against the 10.7 SDK gives you, but any programmer worth his salt should take deprecation as a hint that they should be looking for another way.

There are basically 3 ways to escalate privileges on OS X, and only one of them is now recommended:

  1. Use a helper tool which has its setuid bit set so that it runs as root. Risky if that tool gets compromised, and the setuid bit can be lost, needing reinstatement by another privileged task.
  2. Execute a command as root via AuthorizationExecuteWithPrivileges. As mentioned above, this is now deprecated, and again if a hacker compromises either the app or the tool being launched, bad things can happen.
  3. Ask Launch Services to install a privileged helper tool via SMJobBless. This helper is subsequently run by launchd as root when invoked via a Unix socket, and can perform privileged tasks. Importantly, code signing is verified at both ends by Launch Services at install time to prevent tampering with either binary.

Clearly option 3 was the way to go – the only ‘downside’ about it is that is does require that you have the ability to sign your application and helper tool. I already have valid code signing certificates because I deploy on the App Store, so this isn’t an issue (even though this functionality won’t actually be in the App Store version of SourceTree because Apple disallow installer behaviour there, I still sign with the same certs). In fact, the fact that I know my app and helper tool can’t be interfered with without the code signatures becoming invalid is very reassuring. Given that it only costs $99 per year to be on the Mac Developer Programme which allows you to get certificates (even if you don’t deploy on the App Store), it’s something serious developers should consider strongly.

SMJobBless is ideally suited to installing daemons, but it’s perfectly acceptable to install tools which run simple one-off tasks too. When setting up the plist for the helper, you specify that it is ‘OnDemand’, with no ‘KeepAlive’ which means it’s not started by Launch Services at startup, only when a Unix socket is opened, and shuts down very quickly if there’s no activity. Unfortunately the SMJobBless example doesn’t do anything except to show you how to install a tool, it doesn’t tell you how to implement that tool to do anything useful, or how to call it from your main application.

To see how to do that, you need to refer to BetterAuthorizationSample , which includes a re-usable library for this. Ironically though, this example uses AuthorizationExecuteWithPrivileges to install its helper (this example pre-dated its deprecation in favour of SMJobBless). So you have to remove all the code associated with installation; you won’t need it anyway since SMJobBless does that function better. You keep the rest which gives you a framework for implementing and calling the helper. So here’s what I did:

  1. Implement the installation of a privileged helper, based directly on the SMJobBless example. I needed to change the bundle IDs and the certificate CN’s to match my setup of course.
  2. Extend the plist files from SMJobBless to register the helper with a socket. This was basically a case of copying the settings from the BetterAuthorizationSample plists, which already does this.
  3. Bring in BetterAuthorizationSampleLib.c/.h to assist with implementation of the helper, and the code for calling it in the app, but remove everything in the ‘Installation’ section. This eliminates all the references to AuthorizationExecuteWithPrivileges – we’re doing the install with SMJobBless so don’t need that.
  4. Follow the BetterAuthorizationSample for the implementation of the helper, and the bit in the application where you call the helper to perform privileged operations.

So in my case, the following happens when you click ‘Install Command Line Tool’ in SourceTree:

  1. A privileged helper is installed in Launch Services using SMJobBless. OS X checks the code signatures on both ends to ensure that the helper and the application asking to install it are valid (must be signed with my cert, and that cert must be issued by Apple).
  2. A connection is opened to the privileged helper over a socket which causes launchd to start it up
  3. I ask the helper via the BetterAuthorizationSampleLib to install the command-line tool in /usr/local/bin. As an additional check, the helper validates via ‘codesign -v  -R=”conditions”‘ that this tool is code signed with my cert (again, must be issued by Apple) – this is to prevent anyone else sniffing out this socket and trying to use it to install other things. If that passes, it installs the command.

This is quite a long-winded process compared to just calling a ‘cp’ command via AuthorizationExecuteWithPrivileges, but it’s also a lot more secure, since a malicious person can’t alter any of the moving parts without invalidating the code signatures. You’re also insulated from future changes when inevitably AuthorizationExecuteWithPrivileges is removed entirely.

I apologise for the lack of a pre-packaged example here – I haven’t had time to extract one from my own implementation yet. However, as described above if you start with the SMJobBless sample and add-in the BetterAuthorizationSample, removing from the latter everything associated with installation, you’re basically there. If I get chance later I’ll post a shrink-wrapped example.

I hope that helps someone – I found there to be little information on this subject that was up-to-date, and lots of older information that was misleading so maybe this will save someone some time. Ideally, I hope Apple will combine the SMJobBless and BetterAuthorizationSample some time to produce a 10.7-compliant official example.

  • http://blog.dustinrue.com,http://controlplane.dustinrue.com Dustin Rue

    Did you ever have a chance to post your “shrink wrapped example”? Very interested in it for a project I’m working on

  • http://www.stevestreeting.com Steve

    No, I wasn’t sure anyone was actually interested :)

    I’ll try to find some time to do this, although as I mentioned if you start by combining the 2 Apple examples and remove the installation parts of BetterAuthorizationSample you’re basically there.

  • Jason Buehler

    I would love to see the shrink wrapped example too…

  • Coderama

    Please give us your example. I have been working on this for a week and finally gave up today and tried AuthorizationExecuteWithPrivileges and it was so easy to implement. Then I stumbled on this post…

    Please please please :)

  • http://www.stevestreeting.com Steve

    OK, OK! ;) I should be able to find some time this weekend to pull my usage apart into something that works more generally….

  • Coderama

    Champion! BTW, just read up on SourceTree and it’s acquisition by Atlassian – GREAT WORK!

  • http://www.stevestreeting.com Steve

    OK, here you go. https://bitbucket.org/sinbad/privilegedhelperexample

    Please make sure you read the Readme.txt, and bear in mind you must have a proper code signing certificate to use it (and must plug that in in the right places in the code / info.plist files). I’ve tested this with my codesigning cert and it worked fine.

  • Pingback: SteveStreeting.com » Blog Archive » Follow-up: OS X privilege escalation without using deprecated methods

  • Eric

    First thanks SO much for this example code. I’ve been trying to merge the two examples from ADC and this has helped a lot. Very much appreciated. That said I am not quite there with your example. After making the changes to the code signing cert name it compiles and I get the output from the example tool to the console. But I am never asked to authorize an installation of the helper tool, and as such it appears the helper tool and launchd item are not installed. It turns out that if I munge up the name of my code signing cert the example still compiles and the expected output shows up on the console.

    Any suggestions? I’m running with Xcode 4.2 on Snow Leopard 10.6.8. I feel like I’m almost to the point of grokking this.

    Thanks again for the work!

  • Eric

    Doh. I see my initial problem. What an idiot. I was only compiling the example tool code.

    Sorry about that.

    Now when I compile the proper target I am getting an error around Interface Builder:

    Unable to resolve Interface Builder plug-in dependency for “MainMenu.xib”. Xcode 4 is missing components necessary to load the following class: IBNSLayoutConstraint. Ensure that Xcode has been properly installed.

    Any suggestions… again?

  • http://www.stevestreeting.com Steve

    I build the example with XCode 4.3 so you will need the same to build it unmodified. You probably have an older copy of XCode which doesn’t support what is now the default layout style (instead of springs and struts). Otherwise, you can just change the layout mode, open MainMenu.xib, press Cmd-1 to go to the File Inspector and uncheck the ‘Use Auto Layout’ button. That said, it might not be there if you have an old version of XCode – maybe just try re-saving it with your version. There’s nothing in the sample that needs any new features, this is just how XCode sets things up out of the box now.

  • Eric

    OK. I moved the project to Lion with Xcode 4.3.2. The project compiles fine now.

    Thanks again for the great example.

  • Eric

    Sorry to follow up again. I have a different issue now. The example seemed to be working, then it didn’t. I hadn’t changed anything in the code but building/run now results in:

    error: failed to launch ‘/Volumes/Home/ericwilliams/Library/Developer/Xcode/DerivedData/PrivilegedHelperExample-hiceuiwcjykwgafxvfbmmebjrsik/Build/Products/Debug/PrivilegedHelperExample.app/Contents/MacOS/PrivilegedHelperExample’ — SBTarget is invalid

    I’ve even gone back from scratch, followed all your steps, using a cert from may ADC account but continue to get the above error.

    The only thing I did before having this issue is “uninstall” the file laid out by SMJobBless so I could have a clean setup, as follows:

    unloaded the the launched item
    removed the launched plist.
    removed the privileged helper file
    removed the socket file.

    I’ve left all your default settings in place except for the name of the cert.

    I totally appreciate your comment on many things can go wrong. ;)

    Thanks for any suggestions.

  • http://www.stevestreeting.com Steve

    Never seen that before. Did you mess with the debugger setup? The only reference I can find to this issue is if you try to mix the LLVM compiler with GDB or vice versa. You might want to re-extract the original project file and start again if you can’t find the issue.

  • Eric

    I saw that reference on the debugger as well. I hadn’t touched that setting at all until I had the issue. I then tried switching back and forth to no avail. I also started over from the original download several times and the same result each time. Very odd. I’ve also duplicated this on two different machines as well. I’m baffled.

    Thanks.