Read Me About MoreAuthSample

1.1b1

MoreAuthSample shows how to access privileged functionality from a non-privileged application on Mac OS X. This technique, described in detail in the Authorization Services documentation, is somewhat complicated to implement, and thus a good code sample can be extremely valuable.

MoreAuthSample is designed to support Mac OS X 10.0 and above, although it has only been tested on Mac OS X 10.1 and above.

MoreAuthSample versus AuthSample

MoreAuthSample is a replacement for AuthSample, the original sample that showed this technique. As the two samples share no code, I decided to make a new sample rather than update AuthSample.

MoreAuthSample has the following advantages over AuthSample.

Having said this, I should stress that the basic algorithm used by MoreAuthSample is identical to that used by AuthSample, which means that MoreAuthSample inherits most of the security pros and cons of AuthSample.

Packing List

The sample contains the following items.

Using the Sample

To try out the sample for yourself, launch the MoreSecurityTest application and click the one of the two test buttons. This will issue a command to the embedded helper tool and display the results.

Building the Sample

You can build the sample using Project Builder from the Dec 2002 developer tools. Open the project file (MoreSecurityTest.pbproj), make sure that the "App" target is selected, and choose Build from the Build menu. This will automatically build the application and the helper tool.

The project also builds with CodeWarrior Pro 8.3 on Mac OS X 10.2. Open the project file (MoreSecurityTest.mcp) and choose Make from the Project menu. This will automatically build the application and the helper tool.

Using MoreSecurity In Your Code

To use the MoreSecurity module in your own code, follow these instructions.
  1. Define a request/response protocol based around CFDictionaries. This is as simple as defining the set of keys in the request that describe the operation you want to do, and the set of keys in the response that hold the results of those operations (if any).
  2. Create a helper tool whose main function looks like the one shown in Listing 1. In this listing, MyCommandProc is a callback that is executed when a request is passed to the tool. It can do privileged operations, such as committing changes to System Configuration framework. Its parameters are passed to it as a CFDictionary. It can pass results back to the application by creating a response dictionary.

     Listing 1. The main function for a MoreSecurity-based helper tool

    int main(int argc, const char *argv[])
    {   
        int                 err;
        int                 result;
        AuthorizationRef    auth;
        
        auth = MoreSecHelperToolCopyAuthRef();
    
        err = MoreSecDestroyInheritedEnvironment(
                              kMoreSecKeepStandardFilesMask, 
                              argv);
        if (err == 0) {
            err = MoreUNIXIgnoreSIGPIPE();
        }
        if (err == 0) {
            err = MoreSecHelperToolMain(STDIN_FILENO, 
                                        STDOUT_FILENO, 
                                        auth, 
                                        MyCommandProc, 
                                        argc, 
                                        argv);
        }
    
        return MoreSecErrorToHelperToolResult(err);
    }
  3. Build the helper tool in the "MacOS" folder inside your application package. Decide on two tool names, one for the original template tool (for example, "HelperToolTemplate") and one for the working copy of the tool (for example, "HelperTool").
  4. In your application, use MoreSecCopyHelperToolURLAndCheckBundled to find (or create) the working copy of your tool. The working copy of the helper tool will exist within the user's Application Support folder. See the description of MoreSecCopyHelperToolURLAndCheck[Bundled] (in "MoreSecurity.h") for an explanation of this.
  5. Then use MoreSecExecuteRequestInHelperTool to execute the tool, passing it a request dictionary. On success, you will get back a response dictionary. Use MoreSecGetErrorFromResponse to extract the error result from your tool's MyCommandProc routine.
  6. If you need your helper tool to open privileged descriptors on your behalf, see the comments associated with kMoreSecFileDescriptorsKey (in "MoreSecurity.h") for information on how to do this.
  7. For extra credit, use AuthorizationCopyRights to have your tool be self-limiting, as described in the Authorization Services documentation.
  8. Read the Caveats section of this document for important information about issues raised by this sample.

How it Works

This sample inherits much of its architecture from AuthSample. For a high-level overview of how AuthSample works, look at the Authorization Services documentation. For a detail description of how this sample works, look at the commented code.

This sample's complex design is required for a number of reasons.

By placing the setuid root helper tool within the Application Support folder, the MoreSecurity library allows you to copy the application bundle from one disk to another without breaking it, and without any weird error dialogs. In addition, you can copy the program to another machine entirely, and the only thing the user has to do is to reenter their admin password when they first run the application. Finally, the user's Application Support folder is typically inside their home directory, which is typically on the system volume, and thus is not ignoring privileges.

Caveats

MoreIsBetter code, including MoreSecurity, makes heavy use of asserts to check for programming errors in the debug build. By default these asserts are enabled. If you ship this code in a production system, you must disable these asserts. The tripping of an assert in a privileged program could be used to create a security violation. To disable these asserts, set the NDEBUG compile time variable.

MoreSecurity requires that both the application and the helper tool set the disposition for SIGPIPE to be SIG_IGN. You can set it this way by calling MoreUNIXIgnoreSIGPIPE. If you're using CodeWarrior, you must make sure that you disable SIGPIPE via System framework calls. Do not call the signal function, because MSL provides its own version of signal that does not disable SIGPIPE at the system level. If in doubt, call MoreUNIXIgnoreSIGPIPE, which does the heavy lifting for you (internally it calls sigaction, which MSL does not override).

MoreAuthSample stores the template helper tool in the "MacOS" directory within your package, so that the tool can be found using CFBundleCopyAuxiliaryExecutableURL. AuthSample incorrectly puts the helper tool in the "Resources" directory.

When MoreSecHelperToolMain calls your callback function (the commandProc parameter), your process's EUID is set to its RUID. This is in line with the standard security policy of only setting the EUID to 0 when privileges are required. You can use MoreSecSetPrivilegedEUID to switch to EUID 0 and MoreSecTemporarilySetNonPrivilegedEUID to return to the non-privileged EUID.

The error code returned by MoreSecHelperToolMain indicates whether the invocation of the helper tool was successful. It does not indicate that the command performed by the helper tool worked. Use MoreSecGetErrorFromResponse to get the result status from the helper tool command callback.

Your helper tool must call MoreSecHelperToolCopyAuthRef before it calls MoreSecDestroyInheritedEnvironment. MoreSecDestroyInheritedEnvironment destroys the environmental information that MoreSecHelperToolCopyAuthRef needs to return a meaningful result.

Your application must be prepared to handle the kMoreSecIgnoringPrivsInFolderErr (5370) error result from MoreSecCopyHelperToolURLAndCheckBundled. This indicates that the user's home directory is on a volume whose privileges the system is set to ignore. MoreSecurity can not function correctly in that case. The first version of your application should probably just display a meaningful error message in this case. If you get a significant number of users complaining about this error, you will need to modify your program to handle this error more gracefully (perhaps put the helper tool in the Temporary Items folder). If you are forced to do this, please let me know so that I can add support for this in MoreSecurity.

When MoreSecCopyHelperToolURLAndCheckBundled copies the helper tool from your bundle into the Application Support folder, it only copies the data fork. For the new copy of the helper tool to work properly, it must be a Mach-O executable with no dependence on the resource fork or HFS-specific metadata.

MoreSecExecuteRequestInHelperTool (actually its sub-routine, ExecuteSelfInPrivilegedSelfRepairMode) uses a workaround to make up for the fact that AuthorizationExecuteWithPrivileges does not return the process ID (PID) of the child process [3090277]. Without this workaround, the inopportune termination of another, unrelated, child process could confuse MoreSecurity into reaping the wrong zombie. With this workaround the chance of that happening is minimal, but it's still possible. For optimal results, structure your application so that you don't need to call MoreSecExecuteRequestInHelperTool while your program has outstanding unterminated child processes.

MoreSecurity does not check the helper tool for tampering before it self-repairs. The rationale for this is explained in the comment "Notes on Code Signing" in "MoreSecurity.c".

Unlike AuthSample, MoreAuthSample does not use preauthorization. This is because the preauthorization flag (kAuthorizationFlagPreAuthorize) does nothing on current versions of Mac OS X [2907852]. In fact, by attempting to do preauthorization, AuthSample causes the notorious "two authentication dialogs on first run" problem. When the system is fixed to pay attention to the preauthorization flag, I will revise MoreAuthSample to take advantage of it.

Credits and Version History

If you find any problems with this sample, mail <DTS@apple.com> and I’ll try to fix them up.

1.0a1 (Dec 2002) was given to reviewers.

1.0b1 (Jan 2003) was the first version that shipped to the general public. The improvements over 1.0a1 are purely cosmetic.

1.0b2 (May 2003) is a minor update. The update fixes MoreSecIsFolderIgnoringPrivileges to not return false positives. I also picked up a number of other less relevant changes.

1.1b1 (May 2003) is a an update to support passing descriptors from the helper tool back to the application. This allows you to write a helper tool that, say, binds a low-numbered TCP port on behalf of your application.

Share and Enjoy.

Apple Developer Technical Support
Networking, Communications, Hardware

25 May 2003