Repository navigation
[stable] [flutter_tools] Validate plugin class/package identifiers to prevent GeneratedPluginRegistrant injection - #191294
Conversation
…GeneratedPluginRegistrant injection (flutter#189156) ## Description Plugin `pluginClass`/`dartPluginClass` and the Android `package` are interpolated **verbatim** into the generated `GeneratedPluginRegistrant` source files. `flutter_plugins.dart` renders these templates via `_renderTemplateToFile` → `templateRenderer.renderString(template, context)`, and the renderer's `htmlEscapeValues` defaults to `false`, so the values are emitted with no escaping into Java/Kotlin, Swift, Objective-C and C++ source (e.g. `new {{package}}.{{class}}()`, `{{prefix}}{{class}}.register(...)`, `#import <{{name}}/{{class}}.h>`). The per-platform `validate()` methods in `platform_plugins.dart` only checked that these fields were *strings*, not that they were valid identifiers. As a result a plugin declaration whose `pluginClass`/`package` contains arbitrary source (spaces, `;`, `{}`, `()`, newlines, …) passes validation and that source lands in the consuming app's `GeneratedPluginRegistrant` and is compiled into the app. Because plugins are collected over `computeTransitiveDependencies(...)` with no opt-in from the consuming app, a **transitive** dependency can use this to have arbitrary native code compiled into an app that merely depends on it (via a plain `flutter pub get` / `build` / `run`). This is the same "a package must not escape its declared boundary at build time" boundary enforced for asset paths in flutter#187661 and for pub-cache extraction in CVE-2026-27704. Reproduced end-to-end: a dependency declaring ```yaml flutter: plugin: platforms: macos: pluginClass: "SomePlugin.register(...); <injected statements>; if false { SomePlugin" ``` resulted, after `flutter pub get`, in the injected statements appearing verbatim in `macos/Flutter/GeneratedPluginRegistrant.swift` and `ios/Runner/GeneratedPluginRegistrant.m`. ## Fix Restrict `pluginClass`, `dartPluginClass` and the Android `package` to identifier characters (dot-separated identifiers) in each platform's `validate()`, rejecting the plugin specification otherwise. Legitimate class/package names are unaffected; a value that is not a plain identifier now fails with `Invalid plugin specification <name>`. ## Tests - Added a regression test asserting a `pluginClass` containing injection characters is rejected. - Full `test/general.shard/plugins_test.dart` passes (77/77) — no legitimate plugin specification regresses. ## Pre-launch Checklist - [x] I added new tests to check the change I am making. - [x] All existing and new tests are passing. --------- Co-authored-by: Ben Konyi <bkonyi@google.com>
|
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. |
There was a problem hiding this comment.
Code Review
This pull request introduces validation for plugin identifiers and Dart file names across all supported platforms to prevent arbitrary code injection via pubspec declarations. Unit tests were added to verify that malicious inputs are rejected. The feedback suggests expanding test coverage to also validate fileName for web plugins and dartFileName for other platforms.
| throwsToolExit(message: 'Invalid plugin specification evil_dart_plugin'), | ||
| ); | ||
| }, | ||
| ); |
There was a problem hiding this comment.
While the new tests cover pluginClass and dartPluginClass validation, they do not cover fileName validation for web plugins or dartFileName validation for other platforms. Adding tests for these ensures that path traversal and injection attempts via file names are also correctly rejected and do not regress.
);
testUsingContext(
'Plugin.fromYaml rejects a web plugin whose fileName contains injection',
() async {
const maliciousYaml = '''
platforms:
web:
pluginClass: SafeClass
fileName: "some_file.dart'; evilInjectedCall(); //"
''';
expect(
() => Plugin.fromYaml(
'evil_web_plugin',
'',
loadYaml(maliciousYaml) as YamlMap,
null,
const <String>[],
fileSystem: globals.fs,
isDevDependency: false,
),
throwsToolExit(),
);
},
);
testUsingContext(
'Plugin.fromYaml rejects a platform plugin whose dartFileName contains injection',
() async {
const maliciousYaml = '''
platforms:
android:
dartPluginClass: SafeClass
dartFileName: "some_file.dart'; evilInjectedCall(); //"
''';
expect(
() => Plugin.fromYaml(
'evil_android_plugin',
'',
loadYaml(maliciousYaml) as YamlMap,
null,
const <String>[],
fileSystem: globals.fs,
isDevDependency: false,
),
throwsToolExit(
message: 'The plugin `evil_android_plugin` has an invalid `dartFileName` for platform `android` in pubspec.yaml.',
),
);
},
);References
- Code should be tested and follow the guidance described in the writing effective tests guide. (link)
9ee1431
into
flutter:flutter-3.47-candidate.0
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:
#189156
Impact Description:
Plugin
pluginClass,dartPluginClass, and Androidpackagefields are interpolated verbatim into generatedGeneratedPluginRegistrantnative source files (Swift, Objective-C, Java, Kotlin, C++). Previously, validation only checked that these fields were strings, allowing transitive dependencies with malicious or malformed plugin identifiers to inject arbitrary code intoGeneratedPluginRegistrantduringflutter pub getorflutter build.Changelog Description:
[flutter/189156] Validate plugin class and package identifiers to prevent arbitrary code injection into GeneratedPluginRegistrant.
Workaround:
Inspect transitive dependency
pubspec.yamlplugin configurations manually.Risk:
Test Coverage:
Validation Steps:
pluginClassorpackage.packages/flutter_tools/test/general.shard/plugins_test.dart.