-
Notifications
You must be signed in to change notification settings - Fork 376
fix(jwt): race conditions and IAM reliability issues with identity verification enabled #2609
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
nan-li
wants to merge
7
commits into
feat/identity_verification_5.8
Choose a base branch
from
fix/jwt_misc_race_conditions
base: feat/identity_verification_5.8
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
e38f225
Fix race condition: purge anonymous ops AFTER queue is loaded
nan-li 4f086c6
Replay JWT invalidated event to late-registered listeners
nan-li 098a4f7
Fix IAM fetch stuck after login when identity verification is enabled
nan-li c61f42e
Retry IAM fetch after JWT refresh on 401/403 response
nan-li 60c8d9a
chore: fix for detekt, add doc, simplify return
nan-li cfc3e6e
Fix PR review issues: fallback 401 handling, volatile fields, TOCTOU …
nan-li 569659c
Fix rate-limiter visibility and ConsistencyManager data race
nan-li File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟣 In
ConsistencyManager.resolveConditionsWithID(), the linecompletedConditions.add(Pair(condition, deferred))at line 68 is placed OUTSIDE theif (condition.id == id)block (which closes at line 67), so every condition in the list—regardless of ID match—is added tocompletedConditionsand then removed byconditions.removeAll(completedConditions). This is a pre-existing logic error that the PR wrapped withmutex.withLockbut did not fix; the PR adds a new call site atLoginUserOperationExecutor.kt:234that increases exposure.Extended reasoning...
What the bug is and how it manifests
In
ConsistencyManager.resolveConditionsWithID()(lines 58–74), the statementcompletedConditions.add(Pair(condition, deferred))at line 68 sits outside theif (condition.id == id)block. Thatifblock ends at line 67 (closing brace). As a result, every iteration of the loop—regardless of whethercondition.idmatches—appends tocompletedConditions, and the subsequentconditions.removeAll(completedConditions)removes every registered condition, not just those matchingid.The specific code path that triggers it
Calling
resolveConditionsWithID("IamFetchReadyCondition")when two conditions with different IDs are registered (e.g., type A with ID "IamFetchReadyCondition" and type B with a different static ID) would: (1) complete type A's deferred with null (correct), (2) silently remove type B from the list WITHOUT completing its deferred (incorrect). Type B's coroutine remains suspended on.await()indefinitely—the condition can never be fulfilled because it no longer exists in the list to be checked.Why existing code does not prevent it
The indentation in the diff makes it look as though
completedConditions.add()is inside theifblock, but it is not—there is no closing brace before it for theif. The contrast withcheckConditionsAndComplete()(line 88) makes the intent clear: that method correctly placescompletedConditions.add()insideif (condition.isMet(indexedTokens)), only accumulating entries that actually satisfied the predicate. The PR adds amutex.withLockwrapper (fixing the data-race issue separately reported) but leaves the misplacedadd()unchanged.What the impact would be
In the current codebase all registered conditions are
IamFetchReadyConditioninstances sharing a single static ID constant (IamFetchReadyCondition.ID = "IamFetchReadyCondition"), so every entry matches theifcheck anyway and the bug is a no-op today. However, if any second condition type with a different static ID is ever registered andresolveConditionsWithID(typeA_ID)is called, type B conditions are silently removed with their deferreds uncompleted, permanently hanging any coroutine that calledgetRywDataFromAwaitableCondition()for a type B condition. The PR adds a new call site inLoginUserOperationExecutor.kt:234, increasing the surface area and making the latent bug more likely to manifest as the SDK's consistency manager usage grows.Step-by-step proof
CompletableDeferred.conditionslist has two entries.resolveConditionsWithID("IamFetchReadyCondition")is called.condition.id == "IamFetchReadyCondition"→ true; deferred A is completed with null. ThencompletedConditions.add()appends entry A (correct).condition.id == "IamFetchReadyCondition"→ false (type B); deferred B is NOT completed. ThencompletedConditions.add()appends entry B anyway (incorrect).conditions.removeAll(completedConditions)removes both A and B from the live list.conditionsto be matched by any futuresetRywDataorresolveConditionsWithIDcall.How to fix it
Move
completedConditions.add(Pair(condition, deferred))inside theif (condition.id == id)block, mirroring the pattern incheckConditionsAndComplete():