iOS 11.3 中的 NKLibrary 在初始化时崩溃

NKLibrary crashing on init in iOS 11.3

我们还有一些 NewsstandKit 几年没有更新的旧代码。

出于某种原因,我们自 2017 年以来一直在商店中的应用程序版本现在在 iOS 11.3 中出现一连串的崩溃,这似乎是我们第一次调用 [NKLibrary sharedLibrary] 时造成的应用启动时间。

堆栈跟踪指向 NewsstandKit 深处的网络调用。

Crashed: com.apple.main-thread
EXC_BAD_ACCESS KERN_INVALID_ADDRESS 0x0000000000000000

Crashed: com.apple.main-thread
0  CoreFoundation                 0x1810b63d4 CFBooleanGetValue + 80
1  CFNetwork                      0x181823944 URLRequest::initialize(long, void const**, long, __CFDictionary const*) + 328
2  CFNetwork                      0x1817d7298 _CFURLRequestCreateFromArchiveList + 136
3  CFNetwork                      0x18195bfd4 -[NSURLRequest initWithCoder:] + 1660
4  Foundation                     0x181ba01e8 _decodeObjectBinary + 1720
5  Foundation                     0x181b9fa88 _decodeObject + 308
6  Foundation                     0x181b2e8bc -[NSKeyedUnarchiver decodeObjectOfClasses:forKey:] + 432
7  NewsstandKit                   0x1a24031a4 -[NKAssetDownload initWithCoder:] + 216
8  Foundation                     0x181ba01e8 _decodeObjectBinary + 1720
9  Foundation                     0x181b4d010 -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:] + 1388
10 Foundation                     0x181b53c54 -[NSArray(NSArray) initWithCoder:] + 220
11 Foundation                     0x181ba01e8 _decodeObjectBinary + 1720
12 Foundation                     0x181b9fa88 _decodeObject + 308
13 Foundation                     0x181b2e8bc -[NSKeyedUnarchiver decodeObjectOfClasses:forKey:] + 432
14 NewsstandKit                   0x1a2401a08 -[NKIssue initWithCoder:] + 316
15 Foundation                     0x181ba01e8 _decodeObjectBinary + 1720
16 Foundation                     0x181b4d010 -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:] + 1388
17 Foundation                     0x181b53c54 -[NSArray(NSArray) initWithCoder:] + 220
18 Foundation                     0x181ba01e8 _decodeObjectBinary + 1720
19 Foundation                     0x181b4d010 -[NSKeyedUnarchiver _decodeArrayOfObjectsForKey:] + 1388
20 Foundation                     0x181b4d340 -[NSDictionary(NSDictionary) initWithCoder:] + 224
21 Foundation                     0x181ba01e8 _decodeObjectBinary + 1720
22 Foundation                     0x181b9fa88 _decodeObject + 308
23 Foundation                     0x181b2e8bc -[NSKeyedUnarchiver decodeObjectOfClasses:forKey:] + 432
24 Foundation                     0x181bcac84 -[NSCoder(Exceptions) __tryDecodeObjectForKey:error:decodeBlock:] + 80
25 Foundation                     0x181bca9e4 -[NSCoder decodeTopLevelObjectOfClasses:forKey:error:] + 92
26 Foundation                     0x181c088e4 +[NSKeyedUnarchiver(NSKeyedUnarchiverSecureCodingInitializers) unarchivedObjectOfClasses:fromData:error:] + 140
27 NewsstandKit                   0x1a240042c -[NKLibrary _load] + 272
28 NewsstandKit                   0x1a23fe8b8 -[NKLibrary init] + 568
29 NewsstandKit                   0x1a23fe628 __26+[NKLibrary sharedLibrary]_block_invoke + 92
30 libdispatch.dylib              0x180ae0ae4 _dispatch_client_callout + 16
31 libdispatch.dylib              0x180ae42ec dispatch_once_f$VARIANT$mp + 60
32 NewsstandKit                   0x1a23fe5c8 +[NKLibrary sharedLibrary] + 112
33 MY_APP                         0x1007d29e4 -[MYAPPCLASS MYAPPMETHOD] (MYFILE.m:90)

第 33 行是我们调用 [NKLibrary sharedLibrary].currentlyReadingIssue 的地方,这就是我们现在获得的所有信息。这似乎是在应用启动后发生的,但没有信息是在后台还是前台触发的。

只是好奇有没有其他人看过这个?我唯一的想法是我们需要删除 NewsstandKit.

检查文档以查看 NewsKit 的工作方式是否发生了变化。从 docs 看来,sharedLibrary 可以 return nil,这与您看到的崩溃一致。

sharedLibrary

Returns the shared instance representing the newsstand content library.

Return Value

A singleton instance of the NKLibrary class or nil if the instance couldn’t be created.

我就此联系了 Apple 开发人员技术支持。我收到了非常完整的回复。

我的理解是:

  • 这是 iOS 11.3 NewsStand 中的错误。
  • 它会影响在升级到 iOS 11.3
  • 时排队下载报亭资产的用户
  • 只能通过用户删除他们的应用程序并重新安装来修复。 Apple 可能会解决 iOS 11.4 中的错误,这可能会将它们从 "death loop" 中拯救出来,但在那之前,重新安装是唯一的选择。

Apple 的回应,针对标记进行了轻微编辑:

I grabbed on of the crash reports from your bug (2018-04-12_08-08-18.4485_+0100-41f4ffc57b5f5e16c05c0baaa6a426ff9321cdb5.crash) and took an in-depth look. To start, here’s an edited version of the crash backtrace:

Frame 37 is dispatch calling out to your code.
Frames 36 through 34 is your code starting up Newsstand Kit.
Frames 33 through 30 is +[NKLibrary sharedLibrary] doing the singleton thing.
Frames 29 and 28 are the initialiser for NKLibrary. This clearly does the bulk of its work by unarchiving a keyed archive (frames 27 through 5).
Finally, frame 4 indicates that one of the objects in the library is an NSURLRequest, and the frames leading up to the crash frame, frame 0, is the usual NSURLRequest decoding logic.

Based on the above I started investigating how NKLibrary handles its archiving and unarchiving. While do that I stumbled across what I think is the underlying cause of this bug, namely that in 11.3 we updated Newsstand Kit to use secure coding for its library 34837823>. This is a good thing in general, but it seems to have exposed a latent problem in the secure coding support within NSURLRequest.

Consider the attached test project (Test39132525). It has two buttons:

  • Save Insecure creates a simple keyed archive containing an NSURLRequest.

  • Load Secure tries to load that using a secure coder.

If you run it in the obvious way (run, save, load) it crashes with a backtrace like this:

Thread 6 Crashed:
0  … CFBooleanGetValue + 80 (CFInternal.h:713)
…
4  … -[NSURLRequest initWithCoder:] + 1660 (NSURLRequest.mm:280)
5  … _decodeObjectBinary + 1720 (NSKeyedArchiver.m:2391)
…
27 … +[NSKeyedUnarchiver(NSKeyedUnarchiverSecureCodingInitializers) unarch…
28 … -[NKLibrary _load] + 272 (NKLibrary.m:470)
29 … -[NKLibrary init] + 568 (NKLibrary.m:108)
30 … __26+[NKLibrary sharedLibrary]_block_invoke + 92 (NKLibrary.m:75)
31 … _dispatch_client_callout + 16 (object.m:507)
32 … dispatch_once_f$VARIANT$mp + 60 (once.c:63)
33 … +[NKLibrary sharedLibrary] + 112 (once.h:84)
34 … static NewsstandUtilities.allIssues() + 36 (NewsstandUtilities.swift:…
35 … closure #1 in static NewsstandIOUtility.deleteAllLegacyImagesFromExis…
36 … thunk for @callee_owned () -> () + 36 (WelcomeViewController.swift:0)
37 … _dispatch_call_block_and_release + 24 (init.c:994)
…

That looks very familiar, eh?

I believe that this is what’s happening to your users:

  1. They were running your app on 11.2.6 and had an active asset with an active download. The result was that NKLibrary saved an insecure keyed archive containing an NSURLRequest.

  2. They updated to 11.3.

  3. NKLibrary tried to load its archive securely, which crashes.

Most users don’t see this because they have no active downloads in 11.2.6, and thus the keyed archive doesn’t contain an NSURLRequest.

I’ve asked CFNetwork engineering to see if they can get this fixed sooner rather than later. Depending on how that conversation pans out, I may or may not have a chat with the Newsstand Kit folks to see what they think about a potential workaround.