Debugging a malloc crash within AuthorizationExecuteWithPrivileges

Discussion in 'Mac Programming' started by kainjow, Feb 8, 2011.

  1. kainjow, Feb 8, 2011
    Last edited: Feb 8, 2011

    kainjow Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #1
    I've got this chunk of code that calls AuthorizationExecuteWithPrivileges(). So far I've seen 5 crashes all with the same stack trace, all running as 64-bit on 10.6.x, but unreproducible on my end:

    Code:
    Exception Type:  EXC_BAD_ACCESS (SIGSEGV)
    Exception Codes: KERN_INVALID_ADDRESS at 0x00000001029e77fe
    Crashed Thread:  0  Dispatch queue: com.apple.main-thread
    
    Thread 0 Crashed:  Dispatch queue: com.apple.main-thread
    0   libSystem.B.dylib      0x00007fff8725cf6b small_malloc_from_free_list + 912
    1   libSystem.B.dylib      0x00007fff87259741 szone_malloc_should_clear + 2070
    2   libSystem.B.dylib      0x00007fff87258eea malloc_zone_malloc + 82
    3   libSystem.B.dylib      0x00007fff872571e8 malloc + 44
    4   libSystem.B.dylib      0x00007fff8726e7f2 _pthread_work_internal_init + 180
    5   libSystem.B.dylib      0x00007fff87330231 pthread_workqueue_atfork_child + 39
    6   libSystem.B.dylib      0x00007fff87318ebc _cthread_fork_child + 180
    7   libSystem.B.dylib      0x00007fff872b8923 fork + 83
    8   com.mycompany.myapp    0x000000010004d648 AuthorizationExecuteWithPrivileges
    ...
    Code:
    + ([color=#5c2699]OSStatus[/color])launchAuthorizedTaskForPath:([color=#5c2699]NSString[/color] *)taskPath arguments:([color=#5c2699]NSArray[/color] *)arguments output:([color=#5c2699]NSString[/color] **)output
    {
        [color=#aa0d91]if[/color] (!taskPath || ![[[color=#5c2699]NSFileManager[/color] [color=#2e0d6e]defaultManager[/color]] [color=#2e0d6e]fileExistsAtPath[/color]:taskPath])
            [color=#aa0d91]return[/color] [color=#2e0d6e]errAuthorizationToolExecuteFailure[/color];
        
        [color=#5c2699]AuthorizationRef[/color] authorizationRef = [color=#aa0d91]NULL[/color];
        [color=#5c2699]OSStatus[/color] status = [color=#2e0d6e]AuthorizationCreate[/color]([color=#aa0d91]NULL[/color], [color=#643820]kAuthorizationEmptyEnvironment[/color], [color=#2e0d6e]kAuthorizationFlagDefaults[/color], &authorizationRef);
        [color=#aa0d91]if[/color] (status != [color=#2e0d6e]errAuthorizationSuccess[/color])
            [color=#aa0d91]return[/color] status;
        
        [color=#aa0d91]const[/color] [color=#aa0d91]char[/color] *path = [taskPath [color=#2e0d6e]fileSystemRepresentation[/color]];    
        [color=#5c2699]AuthorizationItem[/color] right = {[color=#643820]kAuthorizationRightExecute[/color], [color=#2e0d6e]strlen[/color](path), ([color=#aa0d91]void[/color] *)path, [color=#2e0d6e]kAuthorizationFlagDefaults[/color]};
        [color=#5c2699]AuthorizationRights[/color] rightSet = {[color=#1c00cf]1[/color], &right};
        [color=#5c2699]AuthorizationFlags[/color] flags = [color=#2e0d6e]kAuthorizationFlagDefaults[/color] | [color=#2e0d6e]kAuthorizationFlagPreAuthorize[/color] | [color=#2e0d6e]kAuthorizationFlagInteractionAllowed[/color] | [color=#2e0d6e]kAuthorizationFlagExtendRights[/color];
        status = [color=#2e0d6e]AuthorizationCopyRights[/color](authorizationRef, &rightSet, [color=#643820]kAuthorizationEmptyEnvironment[/color], flags, [color=#aa0d91]NULL[/color]);
        [color=#aa0d91]if[/color] (status != [color=#2e0d6e]errAuthorizationSuccess[/color])
        {
            [color=#2e0d6e]AuthorizationFree[/color](authorizationRef, [color=#2e0d6e]kAuthorizationFlagDestroyRights[/color]);
            [color=#aa0d91]return[/color] status;
        }
        
        [color=#aa0d91]char[/color] **args = [color=#aa0d91]NULL[/color];
        [color=#aa0d91]if[/color] ((arguments != [color=#aa0d91]nil[/color]) && ([arguments [color=#2e0d6e]count[/color]] > [color=#1c00cf]0[/color]))
        {
            args = [color=#2e0d6e]calloc[/color]([color=#1c00cf]1[/color], [color=#aa0d91]sizeof[/color]([color=#aa0d91]char[/color]*) * ([arguments [color=#2e0d6e]count[/color]] + [color=#1c00cf]1[/color]));
            [color=#3f6e74]NSUInteger[/color] i=[color=#1c00cf]0[/color];
            [color=#aa0d91]for[/color] (i=[color=#1c00cf]0[/color]; i<[arguments [color=#2e0d6e]count[/color]]; i++)
            {
                [color=#5c2699]NSString[/color] *arg = [arguments [color=#2e0d6e]objectAtIndex[/color]:i];
                [color=#aa0d91]if[/color] ([arg [color=#2e0d6e]isKindOfClass[/color]:[[color=#5c2699]NSString[/color] [color=#2e0d6e]class[/color]]])
                    args[i] = ([color=#aa0d91]char[/color] *)[arg UTF8String];
            }
        }
        
        [color=#5c2699]FILE[/color] *file = [color=#aa0d91]NULL[/color];
        status = [color=#2e0d6e]AuthorizationExecuteWithPrivileges[/color](authorizationRef, path, [color=#2e0d6e]kAuthorizationFlagDefaults[/color], args, (output != [color=#aa0d91]NULL[/color] ? &file : [color=#aa0d91]NULL[/color]));
        ...
    Now 5 crashes isn't really that much, since this code runs every single time on launch of the app, for every architecture supported on 10.4 and up, so I'm not overly concerned with it, but I'm guessing that there is some memory corruption going on.

    I've been staring at this code all morning and am going a little crazy. The one idea I have is the call to calloc which *should* properly separate the count and size arguments instead of combining them like a call to malloc. However, would this really make a difference? The only thing I can think of is separating them might help prevent overflows but in this specific situation there is only 1 argument so that shouldn't apply.

    Any other ideas?
     
  2. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #2
    I'm probably not going to be able to be much help, but a few things:
    Where do you free the pointer returned from calloc, assigned to args?
    Have you written a small program that calls this method in a loop with quasi-random arguments and let it run for a few hours/days to see if you ever get a crash?
    Have you run this for edge cases like 0 arguments?

    Best i can think of for now, being wholly unfamiliar with the APIs you're using.

    -Lee
     
  3. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #3
    First, is it running under GC or retain/release?

    Second, calloc() zeros the memory it returns; malloc() doesn't. This is critical to the posted code, since you don't actually put a NULL in the last slot of args. So I would expect malloc() to fail in weird ways.

    Third, I don't think this code does quite what you want:
    Code:
            for (i=0; i<[arguments count]; i++)
            {
                NSString *arg = [arguments objectAtIndex:i];
                if ([arg isKindOfClass:[NSString class]])
                    args[i] = (char *)[arg UTF8String];
            }
    
    Imagine that arguments contains one NSObject followed by one NSString. What will the be result in args? Answer: null, utf8strPtr, null. The first null is wrong. I don't think it's relevant to the crash, but that doesn't make it any less wrong.

    Finally, I might be suspicious of the lifetime of [arg UTF8String]'s returned pointer. It may be worthwhile to copy it to a calloc()'ed buffer whose lifetime you have more control over.

    And are you sure AEWP will work with a NULL ptr passed in args? Again, I don't think it's relevant to the crash, but if arguments is nil or empty, that's what's passed in.
     
  4. Sydde macrumors 68020

    Sydde

    Joined:
    Aug 17, 2009
    #4
    According to the docs, the block returned by -UTF8String will belong to the autorelease pool. I would not pass such a thing to an asynchronous process, and it not being a NSObject, there is no way to prevent its deallocation.
     
  5. kainjow thread starter Moderator emeritus

    kainjow

    Joined:
    Jun 15, 2000
    #5
    After the last line of code above.

    Not yet but that's not a bad idea. However, AuthorizationCopyRights() pops up a dialog box asking for the user's username/password, so I'm not sure how doable that is.

    In the way it's used, arguments should always have 1 or more items in it, but I suppose I should make sure AEWP() still works in that case.

    Thanks. I've already added more checks, such as ensuring fileSystemRepresentation (which can throw an exception) and calloc don't return NULL, and making sure args is NULL terminated (although calloc *should* take care of that).

    retain/release

    As mentioned above I added this in to be explicit, but calloc should handle that, unless I'm misunderstanding something basic here...

    Agreed, I realized this looking it over but this shouldn't affect the crash. Worse case the new process just gets the arguments list truncated.

    I'd be very surprised if AEWP was touching the autorelease pool to affect this.

    After Lee mentioned it, I tested it and didn't see an issue. However, the docs don't mention that you can pass a NULL arguments as they often do (like they do for the last argument). Arguments should always be 1 or more, and I'd be amazed to find that AEWP doesn't handle a NULL arguments. Then again, NSTask's launchedTaskWithLaunchPath throws an exception if you pass a nil arguments. I'll change it to always pass a non-NULL pointer with at least the first element being NULL.
     
  6. chown33 macrumors 604

    Joined:
    Aug 9, 2009
    #6
    It was only in regards to your mention of malloc(). In short, a substitution of malloc(), with no other changes, would be incorrect, because the posted code was relying on calloc()'s zeroing.


    The suspicion was more an avenue for exploration: try it, see if anything changes. I expect it makes no difference, but debugging is often an exercise in challenged expectations.
     
  7. lee1210 macrumors 68040

    lee1210

    Joined:
    Jan 10, 2005
    Location:
    Dallas, TX
    #7
    Maybe just make a version of this that gets the Authorization once, then generates various arguments and runs the rest of the code in a loop? I'm guessing the authorization token will go bad after some number of minutes, so you may only be able to do this N-minutes at a time, but you could likely get a few 10s of thousands of runs in during that time period. The reason I would try to get it crashing in some sort of reproducible way is that now if you make a change, you can't make it crash so you don't know if you've fixed it (unless you really catch the bug red-handed).

    It seems like playing with arguments is going to be the key, because that seems to be what affects the behavior of the code at runtime. The idea of chown33 to get some memory for the arguments array and copy might be a good idea, too. If the char * returned from UTF8String has a lifetime of "sometime", that could certainly result in erroneous behavior "sometime".

    -Lee
     

Share This Page