Skip to content

[CP-stable][web] Fix stack corruption in Skwasm and harden withStackScope API#183022

Merged
auto-submit[bot] merged 1 commit into
flutter:flutter-3.41-candidate.0from
flutteractionsbot:cp-stable-2ba67e99809b85c50377efb121f5d94fd1a7d38a
Mar 3, 2026
Merged

[CP-stable][web] Fix stack corruption in Skwasm and harden withStackScope API#183022
auto-submit[bot] merged 1 commit into
flutter:flutter-3.41-candidate.0from
flutteractionsbot:cp-stable-2ba67e99809b85c50377efb121f5d94fd1a7d38a

Conversation

@flutteractionsbot

@flutteractionsbot flutteractionsbot commented Feb 27, 2026

Copy link
Copy Markdown
Contributor

This pull request is created by automatic cherry pick workflow
Please fill in the form below, and a flutter domain expert will evaluate this cherry pick request.

Issue Link:

What is the link to the issue this cherry-pick is addressing?

#182367

Impact Description:

Skwasm apps no longer crash after a few minutes.

Changelog Description:

On Flutter Web Skwasm, the app no longer crashes after running for a few minutes.

Workaround:

Is there a workaround for this issue?

No.

Risk:

What is the risk level of this cherry-pick?

  • Low
  • Medium
  • High

Test Coverage:

Are you confident that your fix is well-tested by automated tests?

  • Yes
  • No

Validation Steps:

What are the steps to validate that this fix works?

Run one of the example apps from the issue and verify that it still hasn't crashed after a few minutes.

…lutter#182912)

This PR fixes a critical memory crash in Skwasm. The root cause was the
incorrect usage of `withStackScope` with an `async` closure in
`SkwasmSurface.rasterizeToImageBitmaps`.

Because `withStackScope` restores the WASM linear memory stack
immediately after the closure returns, any asynchronous yields (waits)
inside the closure would cause the stack to be reclaimed while the
closure was still potentially using pointers to that memory.

Changes:
- Fixed `SkwasmSurface.rasterizeToImageBitmaps` to use a synchronous
closure and handle initialization outside the stack scope.
- Hardened `withStackScope` to throw a `StateError` at runtime if the
provided closure returns a `Future`, preventing this class of bug from
reoccurring.
- Added a dedicated test suite in
`web_ui/test/skwasm/raw_memory_test.dart` to verify the safety check.

Fixes flutter#182367

## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

If you need help, consider asking for advice on the #hackers-new channel
on [Discord].

**Note**: The Flutter team is currently trialing the use of [Gemini Code
Assist for
GitHub](https://developers.google.com/gemini-code-assist/docs/review-github-code).
Comments from the `gemini-code-assist` bot should not be taken as
authoritative feedback from the Flutter team. If you find its comments
useful you can update your code accordingly, but if you are unsure or
disagree with the feedback, please feel free to wait for a Flutter team
member's review for guidance on which automated comments should be
addressed.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
@flutteractionsbot flutteractionsbot added the cp: review Cherry-picks in the review queue label Feb 27, 2026
@flutteractionsbot

Copy link
Copy Markdown
Contributor Author

@harryterkelsen please fill out the PR description above, afterwards the release team will review this request.

@flutter-dashboard

Copy link
Copy Markdown

This pull request was opened from and to a release candidate branch. This should only be done as part of the official Flutter release process. If you are attempting to make a regular contribution to the Flutter project, please close this PR and follow the instructions at Tree Hygiene for detailed instructions on contributing to Flutter.

Reviewers: Use caution before merging pull requests to release branches. Ensure the proper procedure has been followed.

@github-actions github-actions Bot added engine flutter/engine related. See also e: labels. platform-web Web applications specifically labels Feb 27, 2026

@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

The PR aims to address a critical stack corruption vulnerability in Skwasm by enforcing synchronous execution within the withStackScope API and refactoring rasterizeToImageBitmaps. While these changes correctly address the initial issue, the withStackScope implementation is still vulnerable to stack pointer leaks if its closure throws an exception, potentially leading to a Denial of Service through stack overflow due to the absence of a try...finally block.

Comment on lines +175 to +194
Future<List<DomImageBitmap>> rasterizeToImageBitmaps(List<ui.Picture> pictures) async {
await initialized;
final int callbackId = withStackScope((StackScope scope) {
final Pointer<PictureHandle> pictureHandles = scope
.allocPointerArray(pictures.length)
.cast<PictureHandle>();
for (var i = 0; i < pictures.length; i++) {
pictureHandles[i] = (pictures[i] as SkwasmPicture).handle;
}
return surfaceRenderPictures(handle, pictureHandles, pictures.length);
});
final rasterResult =
(await SkwasmCallbackHandler.instance.registerCallback(callbackId)) as RasterResult;
final RenderResult result = (
imageBitmaps: rasterResult.imageBitmaps.toDart.cast<DomImageBitmap>(),
rasterStartMicros: (rasterResult.rasterStartMilliseconds * 1000).toInt(),
rasterEndMicros: (rasterResult.rasterEndMilliseconds * 1000).toInt(),
);
return result.imageBitmaps;
}

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

This refactoring of rasterizeToImageBitmaps is crucial. By moving the await initialized; outside and ensuring the withStackScope closure is synchronous, it correctly addresses the potential stack corruption issue. The callbackId is now correctly obtained within the synchronous scope, and the asynchronous registerCallback is handled afterwards, aligning with the hardened withStackScope API.

Comment on lines 232 to 244
T withStackScope<T>(T Function(StackScope scope) f) {
final StackPointer stack = stackSave();
final T result = f(StackScope());
assert(
result is! Future,
'withStackScope() closure returned a Future. '
'The closure passed to withStackScope must be synchronous and must not '
'use async/await, because the stack is restored immediately after the '
'closure returns.',
);
stackRestore(stack);
return result;
}

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.

security-medium medium

The assert statement is a critical improvement for preventing asynchronous closures and stack corruption. However, the withStackScope function is still vulnerable to stack pointer leaks if the closure f throws an exception. Without a try...finally block, the stackRestore call can be bypassed, leading to repeated stack memory leaks, a potential stack overflow, and a Denial of Service. The suggested change ensures the stack is always restored.

T withStackScope<T>(T Function(StackScope scope) f) {
  final StackPointer stack = stackSave();
  try {
    final T result = f(StackScope());
    assert(
      result is! Future,
      'withStackScope() closure returned a Future. ' 
      'The closure passed to withStackScope must be synchronous and must not ' 
      'use async/await, because the stack is restored immediately after the ' 
      'closure returns.',
    );
    return result;
  } finally {
    stackRestore(stack);
  }
}

Comment on lines +1 to +37
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:test/bootstrap/browser.dart';
import 'package:test/test.dart';
import 'package:ui/src/engine/skwasm/skwasm_impl.dart';

void main() {
internalBootstrapBrowserTest(() => testMain);
}

void testMain() {
group('withStackScope', () {
test('throws AssertionError when closure is async', () {
expect(
() => withStackScope((scope) async {
return 1;
}),
throwsA(
isA<AssertionError>().having(
(e) => e.message,
'message',
contains('withStackScope() closure returned a Future'),
),
),
);
});

test('works with synchronous closure', () {
final int result = withStackScope((scope) {
return 42;
});
expect(result, 42);
});
});
}

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.

medium

The addition of raw_memory_test.dart with tests for withStackScope is excellent. Specifically, the test for throws AssertionError when closure is async directly validates the hardening applied to the withStackScope API, ensuring that the intended synchronous usage is enforced and tested.

@eyebrowsoffire eyebrowsoffire 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.

LGTM

@eyebrowsoffire eyebrowsoffire added the autosubmit Merge PR when tree becomes green via auto submit App label Mar 3, 2026
@auto-submit auto-submit Bot merged commit e4b8dca into flutter:flutter-3.41-candidate.0 Mar 3, 2026
162 checks passed
engine-flutter-autoroll added a commit to engine-flutter-autoroll/packages that referenced this pull request Mar 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

autosubmit Merge PR when tree becomes green via auto submit App cp: review Cherry-picks in the review queue engine flutter/engine related. See also e: labels. platform-web Web applications specifically

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants