Skip to content

fix: [SDK-4818] resolve TOCTOU race on the current user - #1695

Merged
nan-li merged 3 commits into
mainfrom
nan/sdk-4818
Jul 31, 2026
Merged

fix: [SDK-4818] resolve TOCTOU race on the current user#1695
nan-li merged 3 commits into
mainfrom
nan/sdk-4818

Conversation

@nan-li

@nan-li nan-li commented Jul 30, 2026

Copy link
Copy Markdown
Contributor

Description

One Line Summary

Fix TOCTOU race where an isCurrentUser check then a separate _user mutation can apply data to the wrong user after a concurrent login/logout.

Details

Motivation

Executor callbacks checked whether a request belonged to the current user, then mutated _user in a separate step. A concurrent login()/logout() between those steps could clear or hydrate the wrong user's data.

Scope

  • Adds lock-guarded _user access and currentUser(matching:) so the check and use share one read.
  • Routes fetch/hydrate, logout-on-missing, and tags-merge paths through that accessor.
  • Removes isCurrentUser so "is this the current user?" has a single source of truth (_user).
  • No public API changes.

Testing

Unit testing

  • TOCTOU accessor tests: matching returns the captured user after a concurrent switch; nil when no longer current or no user.
  • Existing fetch-user regression tests updated (stale fetch must not clear the new user; current-user fetch still clears before hydrate).

Manual testing

Not run here — unit coverage exercises the race window; please run OneSignalUserTests locally.

Affected code checklist

  • Notifications
  • Outcomes
  • Sessions
  • In-App Messaging
  • REST API requests
  • Public API changes

Checklist

Overview

  • I have filled out all REQUIRED sections above
  • PR does one thing
  • Any Public API changes are explained in the PR details and conform to existing APIs

Testing

  • I have included test coverage for these changes, or explained why they are not needed
  • All automated tests pass, or I explained why that is not possible
  • I have personally tested this on my device, or explained why that is not possible

Final pass

  • Code is as readable as possible.
  • I have reviewed this PR myself, ensuring it meets each checklist item

Made with Cursor

nan-li and others added 2 commits July 27, 2026 18:30
…allbacks

User executor network callbacks asked `isCurrentUser(...)` and then separately
dereferenced `_user`. A concurrent login/logout landing between those two reads
made the first answer stale, so a response could hydrate one user's properties
and subscriptions onto another, clear a just-switched user's data, or log out a
user the failed request had nothing to do with.

Guard `_user` with a lock and add `currentUser(matching:)`, which reads it once
and returns the instance only if it still owns the request's identity model.
Callers use that returned instance instead of re-reading `_user`, so there is no
window between the check and the mutation. The lock is not held across caller
work, which fires model-store listeners into the operation repo and could
deadlock; handing back the checked instance is what makes this safe.

`clearUserData` now takes the user to clear for the same reason.

Co-Authored-By: Cursor <cursoragent@cursor.com>
…ssor

isCurrentUser answered "is this the current user?" from the identity model
store while currentUser(matching:) answers it from the _user instance. The two
disagree while a login or logout is in flight, which is the same split source
of truth behind the TOCTOU race, so collapse every caller onto the accessor.

Co-authored-by: Cursor <cursoragent@cursor.com>
@nan-li nan-li changed the title fix: [SDK-4818] resolve TOCTOU race on the current user in executor callbacks fix: [SDK-4818] resolve TOCTOU race on the current user Jul 30, 2026
@nan-li
nan-li requested a review from a team July 30, 2026 16:24
Comment thread iOS_SDK/OneSignalSDK/OneSignalUser/Source/OneSignalUserManagerImpl.swift Outdated
…essor actually guarantees

The docstring overclaimed that concurrent login/logout cannot apply one user's data to another; that holds for the returned instance's models, not the shared stores.

Co-authored-by: Cursor <cursoragent@cursor.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants