PDA

View Full Version : Admin privileges within objective-c




Blarged
Jun 30, 2008, 02:09 PM
I have some code that requires admin privileges. What is the best method to prompt for admin password from within the objective-c code?



robbieduncan
Jun 30, 2008, 02:11 PM
You need to drop out of Objective-C to do it properly.

The basic idea is:

1) Create an auxiliary executable that you want to give admin privileges to.
2) Get said privileges
3) Launch auxiliary executable with elevated privileges.

Edit to add: this is a method from my aborted image editor to allow copying of files with elevated privileges (to copy to /Library for example)


// /////////////////////////////////////////////////////////////////////////////
// secureCopy:toPath:authenticate:
//
// Authenticates the user (if required) and calls the SecureCopy binary. If the
// user is not authenticated the SecuryCopy binary will refuse act in a directory
// that the user does not own.
//
// This is heavily based on authapp.c from AuthSample
- (BOOL) secureCopy:(NSString *) source
toPath:(NSString *) destination
authenticate:(BOOL) authenticate
{
char path[MAXPATHLEN];
int comms[2] = {};
int childStatus = 0;
int written;
pid_t pid;

AuthorizationRef authorizationRef;
AuthorizationItem right = { "net.robbieduncan.posterpaint.securecopy", 0, NULL, 0 };
AuthorizationItem admin = { kAuthorizationRightExecute, 0, NULL, 0};
AuthorizationItem rights[2];
rights[0] = right;
rights[1] = admin;
AuthorizationRights rightSet = { 2, rights };
OSStatus status;
MyAuthorizedCommand myCommand;
AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagPreAuthorize | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights;
AuthorizationExternalForm extAuth;

if (authenticate)
{
/// Create a new authorization reference which will later be passed to the tool.
status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, flags, &authorizationRef);
if (status != errAuthorizationSuccess)
{
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: failed to create AuthorizationRef. Return code was %d", status);
return NO;
}

// We only use this were we are copying to /Library so always athorize.
status = AuthorizationCopyRights(authorizationRef, &rightSet, kAuthorizationEmptyEnvironment, flags, NULL);
if (status!=errAuthorizationSuccess)
{
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: failed to authorize. Return code was %d", status);
return NO;

}

// Turn an AuthorizationRef into an external "byte blob" form so it can be transmitted to the authtool.
status = AuthorizationMakeExternalForm(authorizationRef, &extAuth);
if (status!=errAuthorizationSuccess)
{
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: Unable to turn AuthorizationRef into external form. Return code was %d", status);
return NO;
}
}

// Need to find the path to the SecureCopy binary insisde the Framework bundle.
NSBundle *fwBundle = [NSBundle bundleForClass:NSClassFromString(@"RDPPBackgroundController")];
if (fwBundle==nil)
{
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: Unable to determine framework bundle");
return NO;
}
NSString *pathToExe = [fwBundle pathForResource:@"SecureCopy" ofType:nil];
if (pathToExe==nil)
{
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: Unable to determine path to SecureCopy within framework bundle");
return NO;
}


// Create a descriptor pair for interprocess communication.
if (pipe(comms))
{
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: Unable to create pipe for comms");
return NO;
}

// Turn our nice Cocoa path string into a nasty cstring path thing.
[pathToExe getFileSystemRepresentation:path maxLength:MAXPATHLEN];
// And do the same for the source and destination...
[source getFileSystemRepresentation:myCommand.source maxLength:MAXPATHLEN];
[destination getFileSystemRepresentation:myCommand.destination maxLength:MAXPATHLEN];

switch(pid = fork())
{
case 0: // Child
{
char *const envp[] = { NULL };

dup2(comms[0], 0);
close(comms[0]);
close(comms[1]);
execle(path, path, NULL, envp);
_exit(1);
}
case -1: // an error occured
close(comms[0]);
close(comms[1]);
return NO;
default: /* Parent */
break;
}

// Parent
// Don't abort the program if write fails.
signal(SIGPIPE, SIG_IGN);

// Close input pipe since we are not reading from client.
close(comms[0]);

// Write the ExternalizedAuthorization to our output pipe.
if (write(comms[1], &extAuth, sizeof(AuthorizationExternalForm)) != sizeof(AuthorizationExternalForm))
{
close(comms[1]);
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: Failed to write externalized AuthorisationRef to child process");
return NO;
}

// Write the commands we want to execute to our output pipe
written = write(comms[1], &myCommand, sizeof(MyAuthorizedCommand));

// Close output pipe to notify client we are done.
close(comms[1]);

if (written != sizeof(MyAuthorizedCommand))
{
NSLog(@"RDPPFramework NSFileManager+RDPPFrameworkAdditions secureCopy:toPath:authenticate: Wrote wrong number of bytes for command");
return NO;
}

// Wait for the tool to return
if (waitpid(pid, &childStatus, 0) != pid)
return NO;

if (!WIFEXITED(childStatus))
return NO;

AuthorizationFree(authorizationRef, kAuthorizationFlagDestroyRights);

return YES;
}


This depends on an auxiliary executable being in my framework called SecureCopy. It's source is:


// /////////////////////////////////////////////////////////////////////////////
// exitCleanly
//
// Exits the program with the correct status and releases the autorelease pool.
void exitCleanly(int code, NSAutoreleasePool *pool)
{
[pool release];
exit(code);
}
// /////////////////////////////////////////////////////////////////////////////
//
// /////////////////////////////////////////////////////////////////////////////
// createDirectoriesToPath
//
// Creates all required directories up to the given path (but not including the
// last path component). Directories are created with the same ownership as the
// containing directory.
void createDirectoriesToPath(NSString *path)
{
NSString *test = [path stringByDeletingLastPathComponent];
if ([test isEqualToString:@"/"])
{
// Reached root!
return;
}
NSFileManager *fm = [NSFileManager defaultManager];
BOOL isDir;
if ([fm fileExistsAtPath:test isDirectory:&isDir] && isDir)
{
// Directory exists
return;
}
// Create directories up to this point
createDirectoriesToPath(test);
// Get the attributes of the containing directory
NSDictionary *att = [fm fileAttributesAtPath:[test stringByDeletingLastPathComponent] traverseLink:NO];
if (![fm createDirectoryAtPath:test attributes:att])
{
NSLog(@"RDPPFramework SecureCopy createDirectoriesToPath Failed to create directory %@",test);
}
}
// /////////////////////////////////////////////////////////////////////////////
//
// /////////////////////////////////////////////////////////////////////////////
// main
//
// main function for the program. Does more or less everything.
int main (int argc, const char * argv[])
{
// This could be written in straight C but I like NSFileManager etc too much!
NSAutoreleasePool * pool = [[NSAutoreleasePool alloc] init];
MyAuthorizedCommand myCommand;
int bytesRead;

BOOL authenticatedMode = NO;

AuthorizationRef auth;
AuthorizationExternalForm extAuth;

OSStatus status;

// Unlike AuthSample we don't have to worry about running on 10.0 or 10.1. We will use _NSGetExecutablePath and not provide a fallback for OSs without it.
unsigned long path_to_self_size = MAXPATHLEN;
char *path_to_self = NULL;
if (! (path_to_self = malloc(path_to_self_size)))
{
NSLog(@"RDPPFramework SecureCopy main unable to malloc path_to_self");
exitCleanly(-1,pool);
}
if (_NSGetExecutablePath(path_to_self, &path_to_self_size)==-1)
{
// Fix the size.
if (! (path_to_self = realloc(path_to_self, path_to_self_size + 1)))
{
NSLog(@"RDPPFramework SecureCopy main unable to realloc path_to_self");
exitCleanly(-1,pool);
}
if (_NSGetExecutablePath(path_to_self, &path_to_self_size) != 0)
{
NSLog(@"RDPPFramework SecureCopy main unable to get path to self");
exitCleanly(-1,pool);
}
}

if (argc == 2 && !strcmp(argv[1], "--self-repair"))
{
NSLog(@"RDPPFramework SecureCopy main Self-repair. Starting");
// We have started ourself in self-repair mode. This means that this executable is not owned by root and/or the setuid bit is not set. We need to recover that...
struct stat st;
int fd_tool;

/* Recover the passed in AuthorizationRef. */
status = AuthorizationCopyPrivilegedReference(&auth, kAuthorizationFlagDefaults);
if (status)
{
NSLog(@"RDPPFramework SecureCopy main Self-Repair. Did not recover AuthorizationRef. Return code was %d", status);
exitCleanly(-1,pool);
}

/* Open tool exclusively, so noone can change it while we bless it */
fd_tool = open(path_to_self, O_NONBLOCK|O_RDONLY|O_EXLOCK, 0);

if (fd_tool == -1)
{
NSLog(@"RDPPFramework SecureCopy main Self-Repair. Exclusive open while repairing tool failed: %d.",errno);
exitCleanly(-1,pool);
}

if (fstat(fd_tool, &st))
{
NSLog(@"RDPPFramework SecureCopy main Self-Repair. fstat failed");
exitCleanly(-1,pool);
}

if (st.st_uid != 0)
{
fchown(fd_tool, 0, st.st_gid);
}

/* Disable group and world writability and make setuid root. */
fchmod(fd_tool, (st.st_mode & (~(S_IWGRP|S_IWOTH))) | S_ISUID);

close(fd_tool);

NSLog(@"RDPPFramework SecureCopy main Self-repair. Complete");
authenticatedMode = YES;
}
else
{
/* Read the Authorization "byte blob" from our input pipe. */
if (read(0, &extAuth, sizeof(extAuth)) != sizeof(extAuth))
{
NSLog(@"RDPPFramework SecureCopy main Error reading authorization blob from input pipe.");
exitCleanly(-1,pool);
}

/* Restore the externalized Authorization back to an AuthorizationRef */
if (AuthorizationCreateFromExternalForm(&extAuth, &auth))
{
authenticatedMode = NO;
}
else
{
authenticatedMode = YES;
}
}

/* If we are not running as root we need to self-repair. */
if (authenticatedMode && geteuid() != 0)
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Not running as root! Starting self-repair mode.");

int pid;
FILE *commPipe = NULL;
char *arguments[] = { "--self-repair", NULL };
char buffer[1024];
int bytesRead;

/* Set our own stdin and stdout to be the communication channel with ourself. */
status = AuthorizationExecuteWithPrivileges(auth, path_to_self, kAuthorizationFlagDefaults, arguments, &commPipe);
if (status)
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Failed to execute ourselves (%@) with privileges. Return code was %d", [NSString stringWithCString:path_to_self],status);
exitCleanly(-1,pool);
}

/* Read from stdin and write to commPipe. */
for (;;)
{
bytesRead = read(0, buffer, 1024);
if (bytesRead < 1) break;
fwrite(buffer, 1, bytesRead, commPipe);
}

/* Flush any remaining output. */
fflush(commPipe);

/* Close the communication pipe to let the child know we are done. */
fclose(commPipe);

/* Wait for the child of AuthorizationExecuteWithPrivileges to exit. */
int wait_status;
pid = wait(&wait_status);
if (pid == -1 || ! WIFEXITED(wait_status))
{
exitCleanly(-1,pool);
}

/* Exit with the same exit code as the child spawned by AuthorizationExecuteWithPrivileges() */
exitCleanly(WEXITSTATUS(status),pool);
}

/* No need for it anymore */
if (path_to_self)
{
free(path_to_self);
}

/* Read a 'MyAuthorizedCommand' object from stdin. This contains the source and destination... */
bytesRead = read(0, &myCommand, sizeof(MyAuthorizedCommand));

/* Make sure that we received a full 'MyAuthorizedCommand' object */
if (bytesRead == sizeof(MyAuthorizedCommand))
{
NSString *source = [NSString stringWithCString:myCommand.source];
NSString *destination = [NSString stringWithCString:myCommand.destination];
NSFileManager *manager = [NSFileManager defaultManager];
if (![manager fileExistsAtPath:source])
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Source path does not exist");
exitCleanly(-1,pool);
}
createDirectoriesToPath(destination);
// Users can be sneeky! Check that the destination dir is owned by the current user if we are no running in authenticated mode
if (authenticatedMode==NO)
{
int uid = getuid();
NSDictionary *attr = [manager fileAttributesAtPath:[destination stringByDeletingLastPathComponent]
traverseLink:NO];
int dirOwner = [[attr objectForKey:NSFileOwnerAccountID] intValue];
if (uid!=dirOwner)
{
// You must be authenticated to copy into a dir you do not own (overly simplistic but secure)
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Not authenticated and uid!=dirOwner. Not allowed.");
exitCleanly(-1,pool);
}
}
// Finally actually copy the file!
BOOL worked = [manager copyPath:source toPath:destination handler:nil];
if (worked==NO)
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. NSFileManager failed to copy the file.");
exitCleanly(-1,pool);
}
}
else
{
NSLog(@"RDPPFramework SecureCopy main Normal-Mode. Did not read a valid command structure");
exitCleanly(-1,pool);
}

exitCleanly(0,pool);
// You can never reach here, but anyway
return 0;
}


and needs


#include <sys/param.h> // MAXPATHLEN

typedef struct MyAuthorizedCommand
{
// Arguments to operate on
char source[MAXPATHLEN];
char destination[MAXPATHLEN];

} MyAuthorizedCommand;


Do you really want to do this? :p

Blarged
Jun 30, 2008, 02:54 PM
Thanks! Good information.


// This is heavily based on authapp.c from AuthSample


I found MoreAuthSample which also looks to be a good reference. (Although the sample app they give launched but spouted errors :( )


Do you really want to do this? :p

For the app I'm working on it is a must. But there is a lot to wrap my head around here, so that's what I'll start doing. I will most likely come back with a few more questions a little later on. Thanks!

robbieduncan
Jun 30, 2008, 02:58 PM
For the app I'm working on it is a must. But there is a lot to wrap my head around here, so that's what I'll start doing. I will most likely come back with a few more questions a little later on. Thanks!

Well I hope you can get it working: I don't know much more than is in that code. That's the only time I've ever done it and it took ages to get it working the way I wanted it to. All the comments are to enable me to work out why I did that later :D

gnasher729
Jun 30, 2008, 03:11 PM
Just a note why you should do things in a separate application: Once your application has admin privileges, it is dangerous. If an attacker finds any exploits against your application, they are in control of an application with admin privileges and can do lots of damage. By using admin privileges only in a helper application that is usually not running and has for example no web access, that attack vector is closed.