fill the void

Posted
23 November 2008 @ 3pm

Tagged
development

12 Comments

sudo NSTasks with Mac OS X’s Security framework

Recently I found myself needing to perform root-level operations. Two simple things. Writing to a directory that’s root-owned and not group-writable (drwxr-xr-x root:wheel). And running a shell task with sudo. I had no experience with Mac OS X’s security framework before this, so now with the operations working, I wrote up a quick Cocoa application and tutorial to help anyone else encountering these same issues. Apple’s documentation and sample code are great, but I had trouble understanding how to use NSTask to perform the operations. Two caveats. I’m using 10.5, so the Xcode project is 3.1 and the security calls might have changed since 10.4 or before. Moreover, while I think my implementation follows Apple’s recommended solution, I might be wrong. Someone let me know if that’s the case.

Mac OS X’s Security framework revolves around authorization. An application must obtain the user’s authorization, through the familiar name/password dialog, to perform privileged operations. Privileged operations aren’t necessarily root-level ones. They might be application-specific tasks, like viewing payroll information. By requiring authorization, an application can limit the user’s ability to access confidential information or tasks. When the operation is a root-level one, Apple cautions developers to take special care to structure the application to minimize security risks. They recommend the code performing the root-level operations be factored out into a separate tool. The problem is the tool needs to run as root, but to run a program as root, the caller must be root. Apple’s solution to this problem is AuthorizationExecuteWithPrivileges or AEWP as mailing lists refer to it. The method allows a non-root application to start another application as root. Apple thinks the best way to use this method is to start the tool in a non-root context and then have the tool call itself with AuthorizationExecuteWithPrivileges. That way, the caller application is never root, and the security exposure is minimized. Apple dives into much greater detail in their documentation; read through it before deploying an application with root-level operations. Be sure to check out their sample code: BetterAuthorizationSample and AuthForAll. (For the more ambitious, take a look at the BetterAuthorizationSample’s Design and Implementation Rationale.)


I called the project BDAuthorize. It’s a simple UI with two buttons: “Create Text File in /Library/LaunchDaemons” and “Duplicate Text File in /Library/LaunchDaemons”. That folder matches the ownership and permissions I wanted to test. Under the hood is a helper tool that performs the privileged operations. The application creation is boiler-plate, but I’ll go over the helper tool creation.

To create a helper tool in Xcode, I added a “Shell Tool” as a new Target ( “Project” -> “Add Target…” -> “Cocoa” -> “Shell Tool” ). I called mine “AuthHelperTool”. I added “AuthHelperTool.m” as a new Objective-C class file, and then I dragged that file onto AuthHelperTool’s “Compile Sources” under Targets. Be sure to remove the file from the analogous folder under BDAuthorize. Now the .m file will be compiled into AuthHelperTool. I wanted this target embedded in BDAuthorize’s Resources folder, so I dragged AuthHelperTool under Products to BDAuthorize’s “Copy Bundle Resources” under Targets. And as I didn’t want to compile AuthHelperTool separately from BDAuthorize, I added AuthHelperTool as a dependency on BDAuthorize. To do this, I did “Get Info” on BDAuthorize under Targets, went to “General”, and added the tool under “Direct Dependencies”.

One subtle issue I ran into was credential caching. To test a sudo call, I tried it in Terminal before trying it in the helper tool. However, sudo caches credentials for a period of time, so my helper tool succeeded for a while and then started failing. To flush sudo’s cache, just type sudo -k in Terminal. Flushing allowed me to test sudo calls myself and test the helper tool in a pristine state.

Although I performed a simple copy in this example, I could have easily run a launchd command like “sudo launchctl load plist” or any other root-level operation. The security framework takes a bit to understand, but structuring an application to leverage it is straight-forward.

Feel free to either download the zip file or browse the code on Google Code.


12 Comments

Posted by
logicuser
15 January 2009 @ 7am

Hi when I build your Xcode project I don’t see the nib / app loaded is this intended ?
I get the “AuthHelpTool” only in build folder. so far looking thru the code and trying to port for my purpose I didn’t catch the right way to adapt your class into my own app. I want to perform a NSTask with sudo, in my own app filling setArguments:[NSArray ArrayWithObjects: , nil] works fine as long as I don’t need any security framework ( i.e. doing rsync with arguments work), now trying to use your code I fail to understand currently what steps are required.

sorry for asking, but I just started doing cocoa stuff and as comming from UNIX domain I decided to run some tools first, now I’m stuck to implement the security framework required here. any additional tips ?


Posted by
bdunagan
16 January 2009 @ 9pm

I’m sorry about your problems. The project does require Xcode 3.1. I think the settings default to building the helper tool only, so you could try switching the “Active Target” and “Active Executable” from “AuthHelperTool” to “BDAuthorize”. Let me know if that works.

The Mac OS X security framework is complex, but as in UNIX, the complexity is there to make it as robust as possible. Take a look at the docs I linked to; both the docs and the sample code help a lot.


Posted by
Chris
19 May 2009 @ 12pm

Brian, thank you so much for this write up – it has been incredibly helpful.

If anyone is following the above in XCode 3 I had a problem when applying and compiling the code and I hope this helps: – when you add the security framework to your app via right clicking on frameworks > Add existing frameworks > ….

Make sure you add the framework after you’ve created the new shell tool app as described above and that you select the new app as a target, otherwise, when you compile, the security framework will only be available to your main app rather than the bundled AuthHelpTool and on compile you’ll get symbol(s) not found errors.

Thanks again Brian… from a (now) happy beginner in Cocoa


Posted by
fill the void – System Preferences Pane Lock
13 December 2009 @ 6pm

[...] creates a simple prefpane with a button and the lock UI, building on two earlier posts about root-level operations and debugging prefpanes. Clicking the button creates an empty file at /var/log/test.txt, an action [...]


Posted by
Chris
6 March 2010 @ 10pm

Brian,

Thanks, this was very helpful!

I’m trying to expand on this to run tcpdump, but am running into the issue of how to stop tcpdump once it’s running. If I understand correctly, the AuthHelperTool would run tcpdump via an NSTask when called with an argument “start” for example. A separate process of the AuthHelperTool would then use the terminate method of NSTask to stop the task, but since it’s a separate process, it has no knowledge of the original object, so how would you do this?


Posted by
Sharath
20 May 2010 @ 1am

Hi,
Your article was very useful and the example is excellent as well. Going forward with that, I would like to implement credentials caching either at install time or first run so that I don’t see the authorization dialog again .. Could you give me pointers on where to look for information on that?

Thanks..


Posted by
bdunagan
20 May 2010 @ 9pm

@Chris: I know this reply is incredibly late, but I only recently figured out how to do what you ask. I wrote up a blog post about communicating with a privileged tool: http://www.bdunagan.com/2010/04/18/communicating-with-a-privileged-tool/.


Posted by
bdunagan
20 May 2010 @ 9pm

@Sharath: My understanding is that you can’t simply cache the authorization between launches. The user will need to authorize any root-level tool at least every launch. Let me know if I’m mistaken. For more info, I would check out Apple’s Authorization Services Programming Guide.


Posted by
serge
22 July 2010 @ 1pm

I got your project from svn, it is very helpful. however i have a question, how do you debug AuthHelperTool it is a separate target in the project. Im having a hard time debugging it.


Posted by
bdunagan
26 July 2010 @ 9pm

@serge I simply used logging to debug any issues that arose, but StackOverflow says gdb from Terminal can do it: http://stackoverflow.com/questions/1384284/how-does-one-automatically-attach-a-debugger-to-a-process-at-process-start-on-os.


Posted by
Gus
30 July 2010 @ 4pm

I am having a lot of trouble to execute: sudo launchctl load using this solution.
After I call AuthorizationExecuteWithPrivileges I receive “errAuthorizationSuccess”
But on console I get this:

“sudo: no tty present and no askpass program specified”

If I manually run it on a Terminal and run the program again everything works.


Posted by
bdunagan
30 July 2010 @ 10pm

@Gus Not sure about your specific issue, but I know this process works with ‘/bin/launchctl’. Sorry I can’t help more.


Leave a Comment