Skip to content

Fix CFI icall violation in Future callbacks - #1914

Open
a-maurice wants to merge 1 commit into
mainfrom
am-future_fix
Open

Fix CFI icall violation in Future callbacks#1914
a-maurice wants to merge 1 commit into
mainfrom
am-future_fix

Conversation

@a-maurice

@a-maurice a-maurice commented Aug 27, 2026

Copy link
Copy Markdown
Contributor

Description

Provide details of the change, and generalize the change in the PR title above.

Eliminate unsafe reinterpret_cast of TypedCompletionCallback and std::function completion callbacks to FutureBase::CompletionCallback in Future::OnCompletion and Future::AddOnCompletion.

Introduce type-safe trampoline functions and wrapper data structures (TypedCompletionCallbackTrampoline and TypedCompletionCallbackData) in firebase::detail to bridge the typed callback to the base callback signature expected by ReferenceCountedFutureImpl::RunCallback, avoiding undefined behavior and runtime CFI type check aborts.


Testing

Describe how you've tested these changes. Link any manually triggered Integration tests or CPP binary SDK Packaging Github Action workflows, if applicable.


Type of Change

Place an x the applicable box:

  • Bug fix. Add the issue # below if applicable.
  • New feature. A non-breaking change which adds functionality.
  • Other, such as a build process or documentation change.

Notes

  • Bug fixes and feature changes require an update to the Release Notes section of release_build_files/readme.md.
  • Read the contribution guidelines CONTRIBUTING.md.
  • Changes to the public API require an internal API review. If you'd like to help us make Firebase APIs better, please propose your change in a feature request so that we can discuss it together.

@a-maurice a-maurice added the tests-requested: full Trigger a FULL set of integration tests (uses expanded test matrix). label Aug 27, 2026
@github-actions github-actions Bot added tests: in-progress This PR's integration tests are in progress. and removed tests-requested: full Trigger a FULL set of integration tests (uses expanded test matrix). labels Aug 27, 2026
@github-actions

github-actions Bot commented Aug 27, 2026

Copy link
Copy Markdown

Integration test with FLAKINESS (succeeded after retry)

Requested by @a-maurice on commit 126a8dd
Last updated: Thu Aug 27 16:08 PDT 2026
View integration test log & download artifacts

Failures Configs
app_check [TEST] [FLAKINESS] [iOS] [macos] [1/2 ios_device: ios_target]
(1 failed tests)  FirebaseAppCheckTest.TestSignIn
firestore [TEST] [FLAKINESS] [Android] [1/3 os: windows] [1/4 android_device: android_latest]
(1 failed tests)  FilterTest.QueryEmptyWhereComposite
[TEST] [FLAKINESS] [iOS] [macos] [1/2 ios_device: ios_target]
(1 failed tests)  CRASH/TIMEOUT
messaging [TEST] [FLAKINESS] [Android] [1/3 os: macos] [1/4 android_device: android_target]
(1 failed tests)  FirebaseMessagingTest.TestSendMessageToToken
storage [TEST] [FLAKINESS] [Android] [1/3 os: ubuntu] [1/4 android_device: emulator_ftl_latest]
(1 failed tests)  CRASH/TIMEOUT

Add flaky tests to go/fpl-cpp-flake-tracker

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request refactors the Future<T>::OnCompletion and Future<T>::AddOnCompletion implementations to avoid unsafe reinterpret_cast on callbacks, introducing a typed callback data structure and trampoline functions instead. The review feedback highlights critical safety issues regarding null or empty callbacks: passing a null callback can cause a crash if the future is already completed, and wrapping an empty std::function without validation can trigger a std::bad_function_call exception or crash. Suggestions are provided to handle these cases safely by using dummy callbacks or adding validity checks.

Comment on lines +182 to +184
if (callback == nullptr) {
api_->AddCompletionCallback(handle_, nullptr, nullptr, nullptr,
/*clear_existing_callbacks=*/true);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

If callback is nullptr and the future is already completed, passing nullptr to api_->AddCompletionCallback will cause a crash (null pointer dereference) in ReferenceCountedFutureImpl::RunCallback when it attempts to execute the callback. To prevent this, pass a dummy callback that does nothing instead of nullptr.

Suggested change
if (callback == nullptr) {
api_->AddCompletionCallback(handle_, nullptr, nullptr, nullptr,
/*clear_existing_callbacks=*/true);
if (callback == nullptr) {
api_->AddCompletionCallback(handle_, [](const FutureBase&, void*){}, nullptr, nullptr,
/*clear_existing_callbacks=*/true);

Comment on lines +200 to +202
FutureBase::OnCompletion([callback](const FutureBase& future) {
callback(static_cast<const Future<ResultType>&>(future));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If callback is an empty std::function (i.e., !callback), executing it inside the lambda will throw std::bad_function_call or cause an immediate crash if exceptions are disabled. To prevent this, check if the callback is valid. If it is empty, safely clear the existing callback using a dummy callback.

  if (!callback) {
    FutureBase::OnCompletion([](const FutureBase&, void*){}, nullptr);
  } else {
    FutureBase::OnCompletion([callback](const FutureBase& future) {
      callback(static_cast<const Future<ResultType>&>(future));
    });
  }

Comment on lines +229 to +231
return FutureBase::AddOnCompletion([callback](const FutureBase& future) {
callback(static_cast<const Future<ResultType>&>(future));
});

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

If callback is an empty std::function (i.e., !callback), executing it inside the lambda will throw std::bad_function_call or cause an immediate crash if exceptions are disabled. To prevent this, check if the callback is valid and return an empty CompletionCallbackHandle if it is empty.

  if (!callback) {
    return CompletionCallbackHandle();
  }
  return FutureBase::AddOnCompletion([callback](const FutureBase& future) {
    callback(static_cast<const Future<ResultType>&>(future));
  });

@github-actions github-actions Bot added the tests: succeeded This PR's integration tests succeeded. label Aug 27, 2026
@firebase-workflow-trigger firebase-workflow-trigger Bot removed the tests: in-progress This PR's integration tests are in progress. label Aug 27, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests: succeeded This PR's integration tests succeeded.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant