Repository navigation
Mark reviewed LocalAuthentication methods safe - #864
LucaCappelletti94 wants to merge 1 commit into
Conversation
There was a problem hiding this comment.
Thanks for the PR! I think I'd prefer blanket:
unsafe-default-safety.documentation-is-reviewed = true
unsafe-default-safety.bounds-checked-internally = trueWith opt-outs for the things that aren't safe?
LAContext.biometryType,LAContext.domainStateandLARight.statealso stayunsafebecause the framework updates their backing state from its own queue without a lock
I suspect these are safe because LAContext isn't Sendable? Unless the queue (unsure which queue you're talking about?) runs on another thread?
|
Ok to go for blanket safety defaults with explicit opt-outs. My proposed opt-outs are speculations informed by public decompilations of the iOS 18.2 and 26.1 binaries, I am not certain about how well they generalize across versions and variants. In those snapshots, The decompilations appear to show a process-wide
It seems to me that using these methods unwisely could lead to very odd behaviours. I would keep them unsafe under the blanket defaults. |
|
I am spending a little while trying to prove unsafety by provoking crashes or misbehaviour, so we can be surer of the choice made. |
68f3ff6 to
f9a5cd6
Compare
|
Now the translator emits configured I tested that on macOS 26.6.2 constructing I audited all 53 added safety contracts against the Xcode 27 headers, generated selectors, and available runtime evidence. The corrections cover nullable callback errors, deprecated properties, direct initialization, and concurrency statements that lacked support. I was only wondering whether to include permalinks to the lines of the decompiled code in the docs to show the source of the claims. Currently I omitted them, what do you think? Double checked all of the comments with Astra. |
57fdb9e to
895a245
Compare
There was a problem hiding this comment.
Thanks for investigating further.
I tested that on macOS 26.6.2 constructing
LAContextandLARightreally does update process-wide counters without synchronization, so they really should be kept unsafe in my opinion.
Hmm, are you sure it's not just done atomically?
If it really is true that LAContext.new could race with other such calls, that sounds like a bug in Apple's framework?
Is it possible for you to reproduce the racing behaviour somehow?
I was only wondering whether to include permalinks to the lines of the decompiled code in the docs to show the source of the claims. Currently I omitted them, what do you think?
I'd prefer to omit that, since it's not really an autoritative source.
| #[serde(rename = "unsafe")] | ||
| #[serde(default)] | ||
| pub unsafe_: Option<bool>, | ||
| #[serde(rename = "unsafe-reason")] |
There was a problem hiding this comment.
I think this is valuable, but I'd rather have it done separately (because there's a bunch of existing places where such documentation could be useful).
Would you be up for filing a PR doing that across all the translation-config.tomls? Regardless, for this PR I'd be fine with having the safety docs as comments in just the toml.
There was a problem hiding this comment.
If you do get around to making a PR for it, I think I would rather have it like class.LAContext.methods.new.unsafe = "xyz", where it automatically detects string vs boolean, and treats a string as unsafe + safety reason.
There was a problem hiding this comment.
Moved the reasons to TOML comments. I'll do the unsafe = "reason" follow-up once this lands if I get the time.
The decompiled code I linked does not show any sort of thread-awareness, but I did not red team it, I could try to see whether I can make it crash by assuming it is safe.
Wouldn't be the first, and that is going in the top of the list of stuff to fuzz now. Ok on the other notes. |
|
Reproduced the counter race directly this time around.
I could not make (
Also found https://developer.apple.com/forums/thread/710327 which suggests to me somebody slammed into this unsafety. |
895a245 to
3153537
Compare
3153537 to
36f6ae3
Compare
|
Three jobs were cancelled for lack of a GitHub runner. Could you rerun them? |
36f6ae3 to
a85997b
Compare
madsmtm
left a comment
There was a problem hiding this comment.
Ugh, annoying, thanks for reproducing it!
I'm not really happy with this (which is due to no fault of yours), I feel like it doesn't really help to mark these methods as unsafe (since you wouldn't be able to soundly use them in a library, the only sound usage is in an application where you state "I don't use this from any other thread"). Ideally we'd find some way to ensure that these APIs are used from a single thread, but I don't think there is such a way (it's the same situation as with std::env::set_var, we can fix it Rust-only with a Mutex, but that doesn't help with C libraries using these).
Could I get you to file a FB with Apple through their Feedback Assistant? This is also a bug affecting Obj-C and Swift as you found, and Apple should either make the accesses here atomic, mark the APIs as @MainActor, or in some other way give it actor isolation.
Without a reply from Apple, I'm a bit hesitant to mark methods safe, since IIUC there's nothing stopping Apple from changing e.g. LADomainStateBiometry.biometryType to read the process-wide biometry type instead in a future macOS version? And then we'd have to mark that method as unsafe again.
Does that make sense? Sorry for the big asks, but I'm not too familiar with LocalAuthentication, and I'd rather be conservative here.
| # Completion result and error pointers may be null. | ||
| class.LARightStore.methods."rightForIdentifier:completion:".unsafe = true | ||
| class.LARightStore.methods."saveRight:identifier:completion:".unsafe = true | ||
| class.LARightStore.methods."saveRight:identifier:secret:completion:".unsafe = true | ||
| class.LARightStore.methods."removeRight:completion:".unsafe = true | ||
| class.LARightStore.methods."removeRightForIdentifier:completion:".unsafe = true | ||
| class.LARightStore.methods."removeAllRightsWithCompletion:".unsafe = true | ||
| class.LAPrivateKey.methods."signData:secKeyAlgorithm:completion:".unsafe = true | ||
| class.LAPrivateKey.methods."decryptData:secKeyAlgorithm:completion:".unsafe = true | ||
| class.LAPrivateKey.methods."exchangeKeysWithPublicKey:secKeyAlgorithm:secKeyParameters:completion:".unsafe = true | ||
| class.LAPublicKey.methods."exportBytesWithCompletion:".unsafe = true | ||
| class.LAPublicKey.methods."encryptData:secKeyAlgorithm:completion:".unsafe = true | ||
| class.LAPublicKey.methods."verifyData:signature:secKeyAlgorithm:completion:".unsafe = true | ||
| class.LASecret.methods."loadDataWithCompletion:".unsafe = true |
There was a problem hiding this comment.
This is not a soundness problem, since to actually use the pointers, you have to write unsafe { err.as_ref() } or similar in the completion handler. So I think these can be safe.
95e3292 to
7582001
Compare
7582001 to
5f01776
Compare
Yeah these are not "unsafeties" as much as just plain bad design, as "unsafe" does not mean race-y and buggy. In the context of https://github.com/open-source-cooperative/apple-native-keyring-store I am handling these APIs with the necessary care (if the user does not directly call the underlying APIs also elsewhere, which would immediately defeat the purpose)
Yeah, I went Hail Mary and started putting reproductions for each of the API crashes in https://github.com/LucaCappelletti94/apple-sdk-repros alongside the FB where I filed them already. It is rather unfortunate that Apple keeps all FBs private, I get it for CVEs but these are just bugs I believe.
True, and there is no harness we could use to prove safety. When I started with this PR work I honestly expected this to be somewhat more straightforward and while it kindof is for certain methods, there is no guarantee that between versions the same safety guarantee holds. The only thing I can think of would be, in the brave new LLM world, to add some automation that detects safety/unsafety, but I would only ever take the result of an LLM interpretation of a method's safety as a suggestion. Given that these two PRs only aimed at bringing better Rust clarify to the bindings, and make it clear which ones can be considered safe and not, I would suggest to pivot from trying to add the safe/unsafe marker and add documentation of bugginess in some way. |
Thanks a lot!
Yeah, that might be better. Let's wait a while though (probably at least a month if I know Apple Feedback right) and see whether they decide to want to fix it or not. |
Enables the blanket safety defaults for
LocalAuthenticationand keeps an explicitunsafeopt-out only where there is a concrete reason, with that reason written as a comment intranslation-config.toml. The per-instanceLAContextandLARightaccessors become safe, since neither class isSend.The constructors of
LAContextandLARightstay unsafe. On macOS 26.6.2 they increment process-wide instance and command counters with a plain load, add and store, so concurrent construction is a data race.canEvaluatePolicy:error:,biometryType,domainStateandLARight.statestay unsafe because they touch shared state of the same kind, as do the completion-handler methods whose error pointers can be null. Behaviour on other OS releases is unverified.The regenerated bindings are in the generated fork.