Monday, October 7, 2024

Launching Before UserDefaults Is Available

Christian Selig (Mastodon):

It seems at some point, even though UserDefaults is intended for non-sensitive information, it started getting marked as data that needs to be encrypted and cannot be accessed until the user unlocked their device. I don’t know if it’s because Apple found developers were storing sensitive data in there even when they shouldn’t be, but the result is even if you just store something innocuous like what color scheme the user has set for your app, that theme cannot be accessed until the device is unlocked.

[…]

Again, who cares? Users have to unlock the device before launching my app, right? I thought so too! It turns out, even though Apple’s prewarming documentation states otherwise, developers have been reporting for years that that’s just wrong, and your app can effectively be fully launched at any time, including before the device is even unlocked.

Combining this with the previous UserDefaults change, you’re left with the above situation where the app is launched with crucial data just completely unavailable because the device is still locked.

[…]

If you use Live Activities at all, the cool new API that puts activities in your Dynamic Island and Lock Screen, it seems if your app has an active Live Activity and the user reboots their device, virtually 100% of the time the above situation will occur where your app is launched in the background without UserDefaults being available to it.

UserDefaults doesn’t actually report an error, and this can lead to incorrect behavior and data loss.

Christian Selig:

It’s a really rough bug, because if you sometimes can’t trust UserDefaults, it means you can read out data from it with the intent to modify it slightly before saving it back, only now you’re modifying junk data and overwriting the good data 🫤

Quinn (in 2015):

IMO the best way around this is to avoid NSUserDefaults for stuff that you rely on in code paths that can execute in the background. Instead store those settings in your own preferences file, one whose data protection you can explicitly manage[…]

NSFileProtectionCompleteUntilFirstUserAuthentication and prewarming don’t apply on the Mac, but similar problems can occur there. A common cause of support issues is that the customer has entered some data or changed a setting, and the app seems to work normally for that launch, but upon relaunch all the changes are forgotten. macOS doesn’t notify the app that the data was never saved to the .plist file, e.g. because of a file permissions problem or because the user tried to redirect the user defaults storage using a symlink.

Previously:

Update (2024-10-08): Guy English:

This is a very nasty corner of a dead simple API that can lead to data loss for customers. UserDefaults should be as straightforward to use as possible. I’d propose a new UserDefaults domain for “public” data that you’d access via something like: UserDefaults.public.

[…]

As more of app functionality is subsumed by the OS managing a series of plugins this sort of thing must be reliable and foolproof.

[…]

“Changed” may be off the mark here too—I think those security changes were done way back in iOS 7. I suspect what’s happening is that the issue is becoming more apparent due to the differing security contexts code is running under more frequently now. It makes sense to secure the Defaults. But it also makes sense to store non-sensitive defaults there… so it’s a pickle currently.

Pasi Salenius:

In addition to encrypted userdefaults being unavailable on background launches, there is something else that is not working right. I sometimes notice defaults not being available in viewDidLoad, and have received reports of that happening to my users. It usually happens after an OS update and reboot.

Christian Selig:

Yeah that’s part of the whole “full launch” business unfortunately, appDidFinishLaunching and corresponding view controllers will be fully getting built without UserDefaults being available even though the docs on prewarming state otherwise.

Christian Selig:

I wanted to build something small and lightweight that would serve to fix the issues I was encountering with UserDefaults and thus TinyStorage was born! It’s open source so you can use it in your projects too if would like.

7 Comments RSS · Twitter · Mastodon


Apple’s bugs keep getting more embarrassing and keep betraying an increasing lack of understanding of the basic purpose of what they are developing.


Seems pretty absurd to me. If you're storing sensitive or potentially sensitive information in NSUserDefaults you are not using the API correctly. Having every developer write their own version of NSUserDefaults to have a sane API to work with I doubt will solve anything.


What does “because of a file permissions problem” mean? cfprefsd is an unsandboxed, root daemon with write access to even SIP-protected files. How is it unable to solve any permission-related issue?


@Léo I don’t know why, but it seems to have trouble when the file has the wrong owner or no write permission or there’s an ACL in place.


I was just writing some code for an old System 7 app. I made a small error and caused the system to crash. I'm not saying I *want* system errors like that to come back. But I did appreciate that the system had no onerous security features, much less broken ones, and was perfectly happy to let me shoot myself in the foot. I miss that.


Apple have truly lost their way. NSUserDefaults is such a very fundamental API that is part of every beginner tutorial. The easiest way to persist small amounts of data.

Today you have to report the usage of this API in your app's privacy manifest file, and it's also become unreliable. Just wow. How can you screw this up so much.


I found about this the hard way about three years ago with Weathergraph - there are situations where the system launched the app (widgetkit, probably), despite the promises, and all attempts at reading the preferences returned the default values, effectively starting the app with the clean slate user experience again.

I rolled out my own file based storage (based on protobuf) and never looked back.

Leave a Comment