Skip to content

Commit dd2c5a1

Browse files
authored
Plumbing for setting domain network policy (flutter#20218)
1 parent 4746a93 commit dd2c5a1

File tree

15 files changed

+539
-111
lines changed

15 files changed

+539
-111
lines changed

ci/licenses_golden/licenses_flutter

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -729,6 +729,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/Flutte
729729
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
730730
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
731731
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
732+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
733+
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java
732734
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
733735
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java
734736
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java

common/settings.h

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,8 +109,10 @@ struct Settings {
109109
bool enable_dart_profiling = false;
110110
bool disable_dart_asserts = false;
111111

112-
// Used to signal the embedder whether HTTP connections are disabled.
113-
bool disable_http = false;
112+
// Whether embedder only allows secure connections.
113+
bool may_insecurely_connect_to_all_domains = true;
114+
// JSON-formatted domain network policy.
115+
std::string domain_network_policy;
114116

115117
// Used as the script URI in debug messages. Does not affect how the Dart code
116118
// is executed.

lib/io/dart_io.cc

Lines changed: 19 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -16,19 +16,27 @@ using tonic::ToDart;
1616

1717
namespace flutter {
1818

19-
void DartIO::InitForIsolate(bool disable_http) {
20-
Dart_Handle result = Dart_SetNativeResolver(
21-
Dart_LookupLibrary(ToDart("dart:io")), dart::bin::LookupIONative,
22-
dart::bin::LookupIONativeSymbol);
19+
void DartIO::InitForIsolate(bool may_insecurely_connect_to_all_domains,
20+
std::string domain_network_policy) {
21+
Dart_Handle io_lib = Dart_LookupLibrary(ToDart("dart:io"));
22+
Dart_Handle result = Dart_SetNativeResolver(io_lib, dart::bin::LookupIONative,
23+
dart::bin::LookupIONativeSymbol);
2324
FML_CHECK(!LogIfError(result));
2425

25-
// The SDK expects this field to represent "allow http" so we switch the
26-
// value.
27-
Dart_Handle allow_http_value = disable_http ? Dart_False() : Dart_True();
28-
Dart_Handle set_field_result =
29-
Dart_SetField(Dart_LookupLibrary(ToDart("dart:_http")),
30-
ToDart("_embedderAllowsHttp"), allow_http_value);
31-
FML_CHECK(!LogIfError(set_field_result));
26+
Dart_Handle embedder_config_type =
27+
Dart_GetType(io_lib, ToDart("_EmbedderConfig"), 0, nullptr);
28+
FML_CHECK(!LogIfError(embedder_config_type));
29+
30+
Dart_Handle allow_insecure_connections_result = Dart_SetField(
31+
embedder_config_type, ToDart("_mayInsecurelyConnectToAllDomains"),
32+
ToDart(may_insecurely_connect_to_all_domains));
33+
FML_CHECK(!LogIfError(allow_insecure_connections_result));
34+
35+
Dart_Handle dart_args[1];
36+
dart_args[0] = ToDart(domain_network_policy);
37+
Dart_Handle set_domain_network_policy_result = Dart_Invoke(
38+
embedder_config_type, ToDart("_setDomainPolicies"), 1, dart_args);
39+
FML_CHECK(!LogIfError(set_domain_network_policy_result));
3240
}
3341

3442
} // namespace flutter

lib/io/dart_io.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,14 +6,16 @@
66
#define FLUTTER_LIB_IO_DART_IO_H_
77

88
#include <cstdint>
9+
#include <string>
910

1011
#include "flutter/fml/macros.h"
1112

1213
namespace flutter {
1314

1415
class DartIO {
1516
public:
16-
static void InitForIsolate(bool disable_http);
17+
static void InitForIsolate(bool may_insecurely_connect_to_all_domains,
18+
std::string domain_network_policy);
1719

1820
private:
1921
FML_DISALLOW_IMPLICIT_CONSTRUCTORS(DartIO);

runtime/dart_isolate.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,9 @@ DartIsolate::DartIsolate(const Settings& settings,
139139
settings.unhandled_exception_callback,
140140
DartVMRef::GetIsolateNameServer(),
141141
is_root_isolate),
142-
disable_http_(settings.disable_http) {
142+
may_insecurely_connect_to_all_domains_(
143+
settings.may_insecurely_connect_to_all_domains),
144+
domain_network_policy_(settings.domain_network_policy) {
143145
phase_ = Phase::Uninitialized;
144146
}
145147

@@ -263,7 +265,8 @@ bool DartIsolate::LoadLibraries() {
263265

264266
tonic::DartState::Scope scope(this);
265267

266-
DartIO::InitForIsolate(disable_http_);
268+
DartIO::InitForIsolate(may_insecurely_connect_to_all_domains_,
269+
domain_network_policy_);
267270

268271
DartUI::InitForIsolate();
269272

runtime/dart_isolate.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -398,7 +398,8 @@ class DartIsolate : public UIDartState {
398398
std::vector<std::shared_ptr<const fml::Mapping>> kernel_buffers_;
399399
std::vector<std::unique_ptr<AutoFireClosure>> shutdown_callbacks_;
400400
fml::RefPtr<fml::TaskRunner> message_handling_task_runner_;
401-
const bool disable_http_;
401+
const bool may_insecurely_connect_to_all_domains_;
402+
std::string domain_network_policy_;
402403

403404
DartIsolate(const Settings& settings,
404405
TaskRunners task_runners,

shell/common/switches.cc

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -242,8 +242,11 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) {
242242
}
243243
}
244244

245-
settings.disable_http =
246-
command_line.HasOption(FlagForSwitch(Switch::DisableHttp));
245+
settings.may_insecurely_connect_to_all_domains = !command_line.HasOption(
246+
FlagForSwitch(Switch::DisallowInsecureConnections));
247+
248+
command_line.GetOptionValue(FlagForSwitch(Switch::DomainNetworkPolicy),
249+
&settings.domain_network_policy);
247250

248251
// Disable need for authentication codes for VM service communication, if
249252
// specified.

shell/common/switches.h

Lines changed: 9 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -181,12 +181,15 @@ DEF_SWITCH(DisableDartAsserts,
181181
"disabled. This flag may be specified if the user wishes to run "
182182
"with assertions disabled in the debug product mode (i.e. with JIT "
183183
"or DBC).")
184-
DEF_SWITCH(DisableHttp,
185-
"disable-http",
186-
"Dart VM has a master switch that can be set to disable insecure "
187-
"HTTP and WebSocket protocols. Localhost or loopback addresses are "
188-
"exempted. This flag can be specified if the embedder wants this "
189-
"for a particular platform.")
184+
DEF_SWITCH(DisallowInsecureConnections,
185+
"disallow-insecure-connections",
186+
"By default, dart:io allows all socket connections. If this switch "
187+
"is set, all insecure connections are rejected.")
188+
DEF_SWITCH(DomainNetworkPolicy,
189+
"domain-network-policy",
190+
"JSON encoded network policy per domain. This overrides the "
191+
"DisallowInsecureConnections switch. Embedder can specify whether "
192+
"to allow or disallow insecure connections at a domain level.")
190193
DEF_SWITCH(
191194
ForceMultithreading,
192195
"force-multithreading",

shell/platform/android/BUILD.gn

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,8 @@ android_java_sources = [
153153
"io/flutter/embedding/engine/dart/DartExecutor.java",
154154
"io/flutter/embedding/engine/dart/DartMessenger.java",
155155
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
156+
"io/flutter/embedding/engine/loader/ApplicationInfoLoader.java",
157+
"io/flutter/embedding/engine/loader/FlutterApplicationInfo.java",
156158
"io/flutter/embedding/engine/loader/FlutterLoader.java",
157159
"io/flutter/embedding/engine/loader/ResourceExtractor.java",
158160
"io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java",
@@ -430,6 +432,7 @@ action("robolectric_tests") {
430432
"test/io/flutter/embedding/engine/PluginComponentTest.java",
431433
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
432434
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
435+
"test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java",
433436
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
434437
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
435438
"test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java",
Lines changed: 161 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,161 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.embedding.engine.loader;
6+
7+
import android.content.Context;
8+
import android.content.pm.ApplicationInfo;
9+
import android.content.pm.PackageManager;
10+
import android.content.res.XmlResourceParser;
11+
import android.os.Bundle;
12+
import android.security.NetworkSecurityPolicy;
13+
import androidx.annotation.NonNull;
14+
import java.io.IOException;
15+
import org.json.JSONArray;
16+
import org.xmlpull.v1.XmlPullParserException;
17+
18+
/** Loads application information given a Context. */
19+
final class ApplicationInfoLoader {
20+
// XML Attribute keys supported in AndroidManifest.xml
21+
static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
22+
FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME;
23+
static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
24+
FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY;
25+
static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
26+
FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY;
27+
static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
28+
FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY;
29+
static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";
30+
31+
@NonNull
32+
private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
33+
try {
34+
return applicationContext
35+
.getPackageManager()
36+
.getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
37+
} catch (PackageManager.NameNotFoundException e) {
38+
throw new RuntimeException(e);
39+
}
40+
}
41+
42+
private static String getString(Bundle metadata, String key) {
43+
if (metadata == null) {
44+
return null;
45+
}
46+
return metadata.getString(key, null);
47+
}
48+
49+
private static String getNetworkPolicy(ApplicationInfo appInfo, Context context) {
50+
// We cannot use reflection to look at networkSecurityConfigRes because
51+
// Android throws an error when we try to access fields marked as @hide.
52+
// Instead we rely on metadata.
53+
Bundle metadata = appInfo.metaData;
54+
if (metadata == null) {
55+
return null;
56+
}
57+
58+
int networkSecurityConfigRes = metadata.getInt(NETWORK_POLICY_METADATA_KEY, 0);
59+
if (networkSecurityConfigRes <= 0) {
60+
return null;
61+
}
62+
63+
JSONArray output = new JSONArray();
64+
try {
65+
XmlResourceParser xrp = context.getResources().getXml(networkSecurityConfigRes);
66+
xrp.next();
67+
int eventType = xrp.getEventType();
68+
while (eventType != XmlResourceParser.END_DOCUMENT) {
69+
if (eventType == XmlResourceParser.START_TAG) {
70+
if (xrp.getName().equals("domain-config")) {
71+
parseDomainConfig(xrp, output, false);
72+
}
73+
}
74+
eventType = xrp.next();
75+
}
76+
} catch (IOException | XmlPullParserException e) {
77+
return null;
78+
}
79+
return output.toString();
80+
}
81+
82+
private static boolean getUseEmbeddedView(ApplicationInfo appInfo) {
83+
Bundle bundle = appInfo.metaData;
84+
return bundle != null && bundle.getBoolean("io.flutter.embedded_views_preview");
85+
}
86+
87+
private static void parseDomainConfig(
88+
XmlResourceParser xrp, JSONArray output, boolean inheritedCleartextPermitted)
89+
throws IOException, XmlPullParserException {
90+
boolean cleartextTrafficPermitted =
91+
xrp.getAttributeBooleanValue(
92+
null, "cleartextTrafficPermitted", inheritedCleartextPermitted);
93+
while (true) {
94+
int eventType = xrp.next();
95+
if (eventType == XmlResourceParser.START_TAG) {
96+
if (xrp.getName().equals("domain")) {
97+
// There can be multiple domains.
98+
parseDomain(xrp, output, cleartextTrafficPermitted);
99+
} else if (xrp.getName().equals("domain-config")) {
100+
parseDomainConfig(xrp, output, cleartextTrafficPermitted);
101+
} else {
102+
skipTag(xrp);
103+
}
104+
} else if (eventType == XmlResourceParser.END_TAG) {
105+
break;
106+
}
107+
}
108+
}
109+
110+
private static void skipTag(XmlResourceParser xrp) throws IOException, XmlPullParserException {
111+
String name = xrp.getName();
112+
int eventType = xrp.getEventType();
113+
while (eventType != XmlResourceParser.END_TAG || xrp.getName() != name) {
114+
eventType = xrp.next();
115+
}
116+
}
117+
118+
private static void parseDomain(
119+
XmlResourceParser xrp, JSONArray output, boolean cleartextPermitted)
120+
throws IOException, XmlPullParserException {
121+
boolean includeSubDomains = xrp.getAttributeBooleanValue(null, "includeSubdomains", false);
122+
xrp.next();
123+
if (xrp.getEventType() != XmlResourceParser.TEXT) {
124+
throw new IllegalStateException("Expected text");
125+
}
126+
String domain = xrp.getText().trim();
127+
JSONArray outputArray = new JSONArray();
128+
outputArray.put(domain);
129+
outputArray.put(includeSubDomains);
130+
outputArray.put(cleartextPermitted);
131+
output.put(outputArray);
132+
xrp.next();
133+
if (xrp.getEventType() != XmlResourceParser.END_TAG) {
134+
throw new IllegalStateException("Expected end of domain tag");
135+
}
136+
}
137+
138+
/**
139+
* Initialize our Flutter config values by obtaining them from the manifest XML file, falling back
140+
* to default values.
141+
*/
142+
@NonNull
143+
public static FlutterApplicationInfo load(@NonNull Context applicationContext) {
144+
ApplicationInfo appInfo = getApplicationInfo(applicationContext);
145+
// Prior to API 23, cleartext traffic is allowed.
146+
boolean clearTextPermitted = true;
147+
if (android.os.Build.VERSION.SDK_INT >= 23) {
148+
clearTextPermitted = NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
149+
}
150+
151+
return new FlutterApplicationInfo(
152+
getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME),
153+
getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY),
154+
getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY),
155+
getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY),
156+
getNetworkPolicy(appInfo, applicationContext),
157+
appInfo.nativeLibraryDir,
158+
clearTextPermitted,
159+
getUseEmbeddedView(appInfo));
160+
}
161+
}
Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// Copyright 2013 The Flutter Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style license that can be
3+
// found in the LICENSE file.
4+
5+
package io.flutter.embedding.engine.loader;
6+
7+
/** Encapsulates all the information that Flutter needs from application manifest. */
8+
public final class FlutterApplicationInfo {
9+
private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
10+
private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
11+
private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
12+
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";
13+
14+
final String aotSharedLibraryName;
15+
final String vmSnapshotData;
16+
final String isolateSnapshotData;
17+
final String flutterAssetsDir;
18+
final String domainNetworkPolicy;
19+
final String nativeLibraryDir;
20+
final boolean clearTextPermitted;
21+
// TODO(cyanlaz): Remove this when dynamic thread merging is done.
22+
// https://github.com/flutter/flutter/issues/59930
23+
final boolean useEmbeddedView;
24+
25+
public FlutterApplicationInfo(
26+
String aotSharedLibraryName,
27+
String vmSnapshotData,
28+
String isolateSnapshotData,
29+
String flutterAssetsDir,
30+
String domainNetworkPolicy,
31+
String nativeLibraryDir,
32+
boolean clearTextPermitted,
33+
boolean useEmbeddedView) {
34+
this.aotSharedLibraryName =
35+
aotSharedLibraryName == null ? DEFAULT_AOT_SHARED_LIBRARY_NAME : aotSharedLibraryName;
36+
this.vmSnapshotData = vmSnapshotData == null ? DEFAULT_VM_SNAPSHOT_DATA : vmSnapshotData;
37+
this.isolateSnapshotData =
38+
isolateSnapshotData == null ? DEFAULT_ISOLATE_SNAPSHOT_DATA : isolateSnapshotData;
39+
this.flutterAssetsDir =
40+
flutterAssetsDir == null ? DEFAULT_FLUTTER_ASSETS_DIR : flutterAssetsDir;
41+
this.nativeLibraryDir = nativeLibraryDir;
42+
this.domainNetworkPolicy = domainNetworkPolicy == null ? "" : domainNetworkPolicy;
43+
this.clearTextPermitted = clearTextPermitted;
44+
this.useEmbeddedView = useEmbeddedView;
45+
}
46+
}

0 commit comments

Comments
 (0)