User1234
User1234

Reputation: 2412

Very weird issue with AuthorizationExecuteWithPriveleges in Cocoa

I'm using AuthorizationExecuteWithPriveleges to execute bash commands from my App with admin privilege. I have found really weird issue. Here what I'm using

    FILE *pipe=nil;
    OSStatus err;
    AuthorizationRef authorizationRef;
    char *command= "/bin/chmod";
  
    
    char *args[] = {"644","folderPath", nil};

   if(err!=0)
    {
                                err = AuthorizationCreate(nil,
                                       kAuthorizationEmptyEnvironment,
                                       kAuthorizationFlagDefaults,
                                       &authorizationRef);
    }
    NSLog(@"test");
    err = AuthorizationExecuteWithPrivileges(authorizationRef,
                                             command,
                                             kAuthorizationFlagDefaults,
                                             args,
                                             &pipe);  

After calling this function about 40 times, it's starting respond very slowly. And after it is will just die,and freeze application, and I have no idea what is happening to this.It doesn't show the log "test", and doesn't do anything, after calling about 40 times. It doesn't matter what Bash command or what arguments you are using. It still does the same thing. What is wrong with this ? The reason I'm using this, because my App needs to run on 10.5 as well.

Please if someone have idea, what can I do. I really appreciate it. I need ASAP. Thanks

Upvotes: 2

Views: 1159

Answers (4)

ipmcc
ipmcc

Reputation: 29886

Looked at this a bit more, and cooked up the following example, presented without warranty, but which works for me for thousands of invocations of AuthorizationExecuteWithPrivileges without issue:

void DoOtherStuff(AuthorizationRef auth, char* path);

void DoStuff(char* path)
{
    AuthorizationItem foo;
    foo.name = kAuthorizationRightExecute;
    foo.value = NULL;
    foo.valueLength = 0;
    foo.flags = 0;

    AuthorizationRights rights;
    rights.count = 1;
    rights.items = &foo;

    AuthorizationRef authorizationRef;
    OSStatus err = errAuthorizationSuccess;

    if (errAuthorizationSuccess != (err = AuthorizationCreate(NULL,  kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef)))    
    {
        NSLog(@"Error on AuthorizationCreate: %lu", (long)err);
        return;
    }

    for (NSUInteger i = 0; i < 5000; i++)
    {
        NSLog(@"Doing run: %lu", (long)i+1);
        DoOtherStuff(authorizationRef, "/tmp/foo");
    }

    if (errAuthorizationSuccess != (err = AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults)))
    {
        NSLog(@"Error on AuthorizationFree: %lu", (long)err);
        return;
    }
}

void DoOtherStuff(AuthorizationRef authorizationRef, char* path)
{    
    OSStatus err = errAuthorizationSuccess;
    FILE *pipe = NULL;
    @try
    {
        char *args[] = {"644", path, NULL};
        if (errAuthorizationSuccess != (err = AuthorizationExecuteWithPrivileges(authorizationRef,
                                                 "/bin/chmod", kAuthorizationFlagDefaults, args, &pipe)))
        {
            NSLog(@"Error on AuthorizationExecuteWithPrivileges: %lu", (long)err);
            return;
        }

        int stat;
        wait(&stat);

        NSLog(@"Success! Child Process Died!");
    }
    @finally 
    {        
        if (pipe)
            fclose(pipe);
    }
}

What Chris Suter said is dead on. What happens when you call AuthorizationExecuteWithPrivileges is that it fork()s your process and then exec()s the requested process (chmod in this case) from the child process. The child process won't be reaped until someone calls wait(), but that's hard because we don't get the PID of the child out of AuthorizationExecuteWithPrivileges (it would have been returned by fork()). As he said, if you're sure there aren't other threads spawning processes at the same time (i.e. your thread is the only one creating child processes), then you can just call the non-PID specific version of wait() like I do in this example.

If you don't call wait() then what happens is you accumulate these zombie child processes that are all waiting to be reaped. Eventually the OS says "no more."

I feel kinda bad posting this, since it's just a retread of what Chris Suter said; I've upvoted his answer.

For completeness, here's a reworked version of that example that achieves the goal by ignoring SIGCHLD instead of calling wait. It also is presented without warranty, but works for me.

void DoOtherStuff(AuthorizationRef auth, char* path);

void DoStuff(char* path)
{
    AuthorizationItem foo;
    foo.name = kAuthorizationRightExecute;
    foo.value = NULL;
    foo.valueLength = 0;
    foo.flags = 0;

    AuthorizationRights rights;
    rights.count = 1;
    rights.items = &foo;

    AuthorizationRef authorizationRef;
    OSStatus err = errAuthorizationSuccess;

    struct sigaction oldAction;
    struct sigaction newAction;

    newAction.__sigaction_u.__sa_handler = SIG_IGN;
    newAction.sa_mask = 0;
    newAction.sa_flags = 0;

    if(0 != sigaction(SIGCHLD, &newAction, &oldAction))
    {
        NSLog(@"Couldn't ignore SIGCHLD");
        return;
    }

    @try
    {
        if (errAuthorizationSuccess != (err = AuthorizationCreate(NULL,  kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, &authorizationRef)))    
        {
            NSLog(@"Error on AuthorizationCreate: %lu", (long)err);
            return;
        }

        for (NSUInteger i = 0; i < 1000; i++)
        {
            NSLog(@"Doing run: %lu", (long)i+1);
            DoOtherStuff(authorizationRef, "/tmp/foo");
        }

        if (errAuthorizationSuccess != (err = AuthorizationFree(authorizationRef, kAuthorizationFlagDefaults)))
        {
            NSLog(@"Error on AuthorizationFree: %lu", (long)err);
            return;
        }
    }
    @finally 
    {
        const struct sigaction cOldAction = oldAction;
        if(0 != sigaction(SIGCHLD, &cOldAction, NULL))
        {
            NSLog(@"Couldn't restore the handler for SIGCHLD");
            return;
        }

    }
}

void DoOtherStuff(AuthorizationRef authorizationRef, char* path)
{ 
    OSStatus err = errAuthorizationSuccess;
    FILE *pipe = NULL;
    @try
    {
        char *args[] = {"644", path, NULL};
        if (errAuthorizationSuccess != (err = AuthorizationExecuteWithPrivileges(authorizationRef,
                                                 "/bin/chmod", kAuthorizationFlagDefaults, args, &pipe)))
        {
            NSLog(@"Error on AuthorizationExecuteWithPrivileges: %lu", (long)err);
            return;
        }

        NSLog(@"Success!");
    }
    @finally 
    {        
        if (pipe)
            fclose(pipe);
    }
}

Upvotes: 3

craniumonempty
craniumonempty

Reputation: 3535

You should initialize err (due to the first IF statement), because it's no guaranteed to be 0. However, it probably is, so you are skipping AuthorizationCreate, so the authorized session isn't created.

Basically you are passing authorizationRef uninitialized to AuthorizationExecuteWithPrivileges which might be a problem.

Plus like others, I would put AuthorizationFree(authorizationRef,kAuthorizationFlagDefaults); at the end as well when you do use AuthorizationCreate to free the memory.

Also it's worth noting that AuthorizationExecuteWithPrivileges is deprecated as of OS X v10.7, but I think you know that since you said you are trying to run on 10.5

EDIT: You might want to check the status of err too after running AuthorizationCreate

if ( err != errAuthorizationSuccess ) {
  return;
}

... you should check err after AuthorizationExecuteWithPrivileges as well.

Upvotes: 1

ipmcc
ipmcc

Reputation: 29886

I think I might know what's going on here: Try handling the pipe correctly (i.e. don't pass NULL, and make sure you close it). Weird stuff like this also happens with NSTask if you fail to give it a STDIN pipe. This page over at cocoadev.com explains:

An NSTask will break Xcode's debug log entirely if you execute ANYTHING related with sh or bash (including scripts). printf, NSLog; all will cease to function as soon as the task has been launched. Even things like right clicking on an object in the debugger will yield nothing (straight GDB still prints though). ... I figured out that the problem lies with standard input, of all things. A quick fix for this would be to set your standard input up to something random, like a pipe, and do nothing with it.

This stumped me for hours and hours (albiet with NSTask and not AS). I would be surprised if the odd behavior you're seeing isn't related. Make sure you're not passing NULL, and then make sure that you're cleaning up the file handle that AuthorizationExecuteWithPrivileges creates for you, by calling fclose, etc.

I'm not 100% sure, but the "NSLog stopped working" symptom caught my eye.

Upvotes: 0

Chris Suter
Chris Suter

Reputation: 2927

What you’re trying to do is not a good idea.

I would guess that you have a bug else where in your code, perhaps in the monitoring of the pipe. We need to see the rest of your code.

If you do pursue this approach, you will need to take care and make sure that you clean up zombie processes which can be awkward when using AuthorizationExecuteWithPrivileges because you don’t get the child process ID. You’ll either need to ignore SIGCHLD, or if you can be certain there are no other threads that are doing things with processes at the same time, you can just issue a call to wait.

You’ve also got to make sure you clean up the pipe as otherwise you’ll run out of file descriptors.

The system is far less forgiving about you leaking file descriptors or processes than it is about leaking memory.

The correct approach for your problem is probably to write a helper tool and then communicate with your helper tool asking it to perform privileged operations on your behalf. That way you’ll only be running your helper tool once. You should be able to read more about this in Apple’s documentation.

Upvotes: 2

Related Questions