AppKit State Restoration on macOS 14 Sonoma

Published on

AppKit State Restoration on macOS 14 Sonoma

AppKit state restoration behaviour changed on macOS 14 Sonoma in a subtle way that can lead to apps not restoring their state correctly. The change can lead to silent breakages which can be hard to debug.

Behavioural Changes

The AppKit release notes for macOS 14 state:

Secure coding is automatically enabled for restorable state for applications linked on the macOS 14.0 SDK. Applications that target prior versions of macOS should implement NSApplicationDelegate.applicationSupportsSecureRestorableState() to return true so it’s enabled on all supported OS versions.

As usual, the behavioural changes only apply to apps that have been linked against the latest SDK to preserve backwards compatibility for existing apps.

Consequences

The relase notes don’t make it immediately clear what the consequences of automatically enabling secure coding might be. In practice, it means that secure coding violations can now occur. The response to such violations depends on the value of NSCoder’s decodingFailurePolicy property which can be one of:

The decodingFailurePolicy property is readonly, thus outside of our control as the NSCoder object is created by the framework. AppKit uses NSDecodingFailurePolicySetErrorAndReturn as the default policy for state restoration.

The documentation states:

On decode failure, the NSCoder will capture the failure as an NSError, and prevent further decodes (by returning 0 / nil equivalent as appropriate).

Thus, after a secure coding violation, subsequent decoding operations would silently fail.

Secure Coding Violations

NSSecureCoding docs demonstrate the canonical secure coding violation:

Historically, many classes decoded instances of themselves like this:

id obj = [decoder decodeObjectForKey:@"myKey"];
if (![obj isKindOfClass:[MyClass class]]) { /* ...fail... */ }

This technique is potentially unsafe because by the time you can verify the class type, the object has already been constructed, and if this is part of a collection class, potentially inserted into an object graph.

So any usages of -[NSCoder decodeObjectForKey:] would trigger a secure coding violation. The docs for NSCoder.decodingFailurePolicy provide further examples:

A decode call can fail for the following reasons:

  • …snip…

  • A secure coding violation occurs. This happens when you attempt to decode an object that doesn’t conform to NSSecureCoding. This also happens when the encoded type doesn’t match any of the types passed to decodeObject(of:forKey:).

How to Debug

Violations can now arise in any -restoreStateWithCoder: implementations, so they need to be audited.

References

← Back to Writings