iosobjective-ccrashafnetworkingxauth

Bug in library causing hundreds of crashes, but I can't pinpoint it. I have the crash data from users, what is causing this crash?


In order to authenticate Instapaper, which uses XAuth, I use AFXAuthClient which is an extension to AFNetworking 1.0 that adds XAuth support for authenticating.

It works really well, for 99% of my users. But for a few dozen, it's caused a TON of crashes (far more than any other crash in my app). My app uses Crashlytics, so I have information on each crash, but I can't quite figure out how to fix it, or even how to recreate it.

The Information

Crashlytics gives me this for the error message:

Fatal Exception: NSInvalidArgumentException * setObjectForKey: object cannot be nil (key: oauth_token)

And this for the logs:

Thread : Fatal Exception: NSInvalidArgumentException
0  CoreFoundation                 0x2e355f4b __exceptionPreprocess + 130
1  libobjc.A.dylib                0x386e56af objc_exception_throw + 38
2  CoreFoundation                 0x2e291667 -[__NSDictionaryM setObject:forKey:] + 818
3  Syllable                       0x0007511f -[AFXAuthClient authorizationHeaderWithRequest:parameters:] + 224 (AFXAuthClient.m:224)
4  Syllable                       0x000752ad -[AFXAuthClient requestWithMethod:path:parameters:] + 239 (AFXAuthClient.m:239)
5  Syllable                       0x00069377 -[AppDelegate loadInstapaperArticles] + 356 (AppDelegate.m:356)
6  Syllable                       0x000680fb -[AppDelegate application:performFetchWithCompletionHandler:] + 137 (AppDelegate.m:137)
7  UIKit                          0x30d469d1 -[UIApplication _handleOpportunisticFetchWithSequenceNumber:] + 448
8  UIKit                          0x30b38fbb -[UIApplication _callInitializationDelegatesForURL:payload:suspended:] + 2010
9  UIKit                          0x30b33353 -[UIApplication _runWithURL:payload:launchOrientation:statusBarStyle:statusBarHidden:] + 714
10 UIKit                          0x30ace41f -[UIApplication handleEvent:withNewEvent:] + 3130
11 UIKit                          0x30acd721 -[UIApplication sendEvent:] + 72
12 UIKit                          0x30b32b3d _UIApplicationHandleEvent + 664
13 GraphicsServices               0x32f6970d _PurpleEventCallback + 608
14 GraphicsServices               0x32f692f7 PurpleEventCallback + 34
15 CoreFoundation                 0x2e3209df __CFRUNLOOP_IS_CALLING_OUT_TO_A_SOURCE1_PERFORM_FUNCTION__ + 34
16 CoreFoundation                 0x2e32097b __CFRunLoopDoSource1 + 346
17 CoreFoundation                 0x2e31f14f __CFRunLoopRun + 1398
18 CoreFoundation                 0x2e289c27 CFRunLoopRunSpecific + 522
19 CoreFoundation                 0x2e289a0b CFRunLoopRunInMode + 106
20 UIKit                          0x30b31dd9 -[UIApplication _run] + 760
21 UIKit                          0x30b2d049 UIApplicationMain + 1136
22 Syllable                       0x0003817f main + 16 (main.m:16)
23 libdyld.dylib                  0x38bedab7 start + 2

Apparently the app is passing nil to setObjectForKey:. Here's where it says it occurs (I've put an --> arrow by the line) in AFXAuthClient.m (the implementation file for the AFXAuthClient library):

- (NSMutableDictionary *)authorizationHeaderWithRequest:(NSURLRequest *)request parameters:(NSDictionary *)parameters
{
    NSMutableDictionary *authorizationHeader = [[NSMutableDictionary alloc] initWithDictionary:@{@"oauth_nonce": _nonce,
                                                @"oauth_signature_method": @"HMAC-SHA1",
                                                @"oauth_timestamp": _timestamp,
                                                @"oauth_consumer_key": self.consumerKey,
                                                @"oauth_signature": AFHMACSHA1Signature([self baseStringWithRequest:request parameters:parameters], _consumerSecret, _token.secret),
                                                @"oauth_version": @"1.0"}];
    if (self.token)
-->      [authorizationHeader setObject:RFC3986EscapedStringWithEncoding(self.token.key, NSUTF8StringEncoding) forKey:@"oauth_token"];

    return authorizationHeader;
}

In the RFC3986EscapedStringWithEncoding() function it calls, it states the following at the beginning:

// Escape per RFC 3986 standards as required by OAuth. Previously, not
// escaping asterisks (*) causes passwords with * to fail in
// Instapaper authentication

My users are indeed logging in with Instapaper so it seems like this library has had issues with Instapaper in the past. I'm not sure what is causing it in this case, or even how to reproduce it.

My only theory was that Instapaper allows you to create accounts without a password, so when a user is logging in without a password, maybe it's passing nil to setObjectForKey? But nope, I tried with a password-less account and my app did not crash at all.

What could be causing this issue? How would I fix it? If there's more information I can provide from Crashlytics please just say so.


Solution

  • First, make sure you're using the latest version - I see a fix from 7 months ago for this issue.

    Second, if you're up-to-date, I would take the approach of modifying AFXAuthClient like this:

    if (self.token) {
        NSString *escapedToken = RFC3986EscapedStringWithEncoding(self.token.key, NSUTF8StringEncoding);
        if (escapedToken) {
            /* guaranteed not to crash here */
            [authorizationHeader setObject:escapedToken forKey:@"oauth_token"];
        } else {
            /* Log self.token you can inspect the types of tokens that are causing
               invalid escapedTokens, perhaps using a service like Flurry. */
        }
    }
    

    This will allow you to collect whatever data is causing RFC3986EscapedStringWithEncoding to return nil. Obviously, be careful how you store/transmit this data, since it is a user's auth token.

    Plus, it should turn those crashes into failed authentication errors, which is (probably) better.