Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.

Plumbing for setting domain network policy #20218

Merged
merged 12 commits into from
Aug 13, 2020
Merged
2 changes: 2 additions & 0 deletions ci/licenses_golden/licenses_flutter
Original file line number Diff line number Diff line change
Expand Up @@ -729,6 +729,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/Flutte
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ResourceExtractor.java
FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java
Expand Down
6 changes: 4 additions & 2 deletions common/settings.h
Original file line number Diff line number Diff line change
Expand Up @@ -109,8 +109,10 @@ struct Settings {
bool enable_dart_profiling = false;
bool disable_dart_asserts = false;

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

// Used as the script URI in debug messages. Does not affect how the Dart code
// is executed.
Expand Down
30 changes: 19 additions & 11 deletions lib/io/dart_io.cc
Original file line number Diff line number Diff line change
Expand Up @@ -16,19 +16,27 @@ using tonic::ToDart;

namespace flutter {

void DartIO::InitForIsolate(bool disable_http) {
Dart_Handle result = Dart_SetNativeResolver(
Dart_LookupLibrary(ToDart("dart:io")), dart::bin::LookupIONative,
dart::bin::LookupIONativeSymbol);
void DartIO::InitForIsolate(bool may_insecurely_connect_to_all_domains,
std::string domain_network_policy) {
Dart_Handle io_lib = Dart_LookupLibrary(ToDart("dart:io"));
Dart_Handle result = Dart_SetNativeResolver(io_lib, dart::bin::LookupIONative,
dart::bin::LookupIONativeSymbol);
FML_CHECK(!LogIfError(result));

// The SDK expects this field to represent "allow http" so we switch the
// value.
Dart_Handle allow_http_value = disable_http ? Dart_False() : Dart_True();
Dart_Handle set_field_result =
Dart_SetField(Dart_LookupLibrary(ToDart("dart:_http")),
ToDart("_embedderAllowsHttp"), allow_http_value);
FML_CHECK(!LogIfError(set_field_result));
Dart_Handle embedder_config_type =
Dart_GetType(io_lib, ToDart("_EmbedderConfig"), 0, nullptr);
FML_CHECK(!LogIfError(embedder_config_type));

Dart_Handle allow_insecure_connections_result = Dart_SetField(
embedder_config_type, ToDart("_mayInsecurelyConnectToAllDomains"),
ToDart(may_insecurely_connect_to_all_domains));
FML_CHECK(!LogIfError(allow_insecure_connections_result));

Dart_Handle dart_args[1];
dart_args[0] = ToDart(domain_network_policy);
Dart_Handle set_domain_network_policy_result = Dart_Invoke(
embedder_config_type, ToDart("_setDomainPolicies"), 1, dart_args);
FML_CHECK(!LogIfError(set_domain_network_policy_result));
}

} // namespace flutter
4 changes: 3 additions & 1 deletion lib/io/dart_io.h
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,16 @@
#define FLUTTER_LIB_IO_DART_IO_H_

#include <cstdint>
#include <string>

#include "flutter/fml/macros.h"

namespace flutter {

class DartIO {
public:
static void InitForIsolate(bool disable_http);
static void InitForIsolate(bool may_insecurely_connect_to_all_domains,
std::string domain_network_policy);

private:
FML_DISALLOW_IMPLICIT_CONSTRUCTORS(DartIO);
Expand Down
7 changes: 5 additions & 2 deletions runtime/dart_isolate.cc
Original file line number Diff line number Diff line change
Expand Up @@ -139,7 +139,9 @@ DartIsolate::DartIsolate(const Settings& settings,
settings.unhandled_exception_callback,
DartVMRef::GetIsolateNameServer(),
is_root_isolate),
disable_http_(settings.disable_http) {
may_insecurely_connect_to_all_domains_(
settings.may_insecurely_connect_to_all_domains),
domain_network_policy_(settings.domain_network_policy) {
phase_ = Phase::Uninitialized;
}

Expand Down Expand Up @@ -263,7 +265,8 @@ bool DartIsolate::LoadLibraries() {

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

DartIO::InitForIsolate(disable_http_);
DartIO::InitForIsolate(may_insecurely_connect_to_all_domains_,
domain_network_policy_);

DartUI::InitForIsolate();

Expand Down
3 changes: 2 additions & 1 deletion runtime/dart_isolate.h
Original file line number Diff line number Diff line change
Expand Up @@ -398,7 +398,8 @@ class DartIsolate : public UIDartState {
std::vector<std::shared_ptr<const fml::Mapping>> kernel_buffers_;
std::vector<std::unique_ptr<AutoFireClosure>> shutdown_callbacks_;
fml::RefPtr<fml::TaskRunner> message_handling_task_runner_;
const bool disable_http_;
const bool may_insecurely_connect_to_all_domains_;
std::string domain_network_policy_;

DartIsolate(const Settings& settings,
TaskRunners task_runners,
Expand Down
7 changes: 5 additions & 2 deletions shell/common/switches.cc
Original file line number Diff line number Diff line change
Expand Up @@ -242,8 +242,11 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) {
}
}

settings.disable_http =
command_line.HasOption(FlagForSwitch(Switch::DisableHttp));
settings.may_insecurely_connect_to_all_domains = !command_line.HasOption(
FlagForSwitch(Switch::DisallowInsecureConnections));

command_line.GetOptionValue(FlagForSwitch(Switch::DomainNetworkPolicy),
&settings.domain_network_policy);

// Disable need for authentication codes for VM service communication, if
// specified.
Expand Down
15 changes: 9 additions & 6 deletions shell/common/switches.h
Original file line number Diff line number Diff line change
Expand Up @@ -181,12 +181,15 @@ DEF_SWITCH(DisableDartAsserts,
"disabled. This flag may be specified if the user wishes to run "
"with assertions disabled in the debug product mode (i.e. with JIT "
"or DBC).")
DEF_SWITCH(DisableHttp,
"disable-http",
"Dart VM has a master switch that can be set to disable insecure "
"HTTP and WebSocket protocols. Localhost or loopback addresses are "
"exempted. This flag can be specified if the embedder wants this "
"for a particular platform.")
DEF_SWITCH(DisallowInsecureConnections,
"disallow-insecure-connections",
"By default, dart:io allows all socket connections. If this switch "
"is set, all insecure connections are rejected.")
DEF_SWITCH(DomainNetworkPolicy,
"domain-network-policy",
"JSON encoded network policy per domain. This overrides the "
"DisallowInsecureConnections switch. Embedder can specify whether "
"to allow or disallow insecure connections at a domain level.")
DEF_SWITCH(
ForceMultithreading,
"force-multithreading",
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/android/BUILD.gn
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,8 @@ android_java_sources = [
"io/flutter/embedding/engine/dart/DartExecutor.java",
"io/flutter/embedding/engine/dart/DartMessenger.java",
"io/flutter/embedding/engine/dart/PlatformMessageHandler.java",
"io/flutter/embedding/engine/loader/ApplicationInfoLoader.java",
"io/flutter/embedding/engine/loader/FlutterApplicationInfo.java",
"io/flutter/embedding/engine/loader/FlutterLoader.java",
"io/flutter/embedding/engine/loader/ResourceExtractor.java",
"io/flutter/embedding/engine/mutatorsstack/FlutterMutatorView.java",
Expand Down Expand Up @@ -430,6 +432,7 @@ action("robolectric_tests") {
"test/io/flutter/embedding/engine/PluginComponentTest.java",
"test/io/flutter/embedding/engine/RenderingComponentTest.java",
"test/io/flutter/embedding/engine/dart/DartExecutorTest.java",
"test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java",
"test/io/flutter/embedding/engine/plugins/shim/ShimPluginRegistryTest.java",
"test/io/flutter/embedding/engine/renderer/FlutterRendererTest.java",
"test/io/flutter/embedding/engine/systemchannels/RestorationChannelTest.java",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,161 @@
// 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.

package io.flutter.embedding.engine.loader;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.XmlResourceParser;
import android.os.Bundle;
import android.security.NetworkSecurityPolicy;
import androidx.annotation.NonNull;
import java.io.IOException;
import org.json.JSONArray;
import org.xmlpull.v1.XmlPullParserException;

/** Loads application information given a Context. */
final class ApplicationInfoLoader {
// XML Attribute keys supported in AndroidManifest.xml
static final String PUBLIC_AOT_SHARED_LIBRARY_NAME =
FlutterLoader.class.getName() + '.' + FlutterLoader.AOT_SHARED_LIBRARY_NAME;
static final String PUBLIC_VM_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.VM_SNAPSHOT_DATA_KEY;
static final String PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.ISOLATE_SNAPSHOT_DATA_KEY;
static final String PUBLIC_FLUTTER_ASSETS_DIR_KEY =
FlutterLoader.class.getName() + '.' + FlutterLoader.FLUTTER_ASSETS_DIR_KEY;
static final String NETWORK_POLICY_METADATA_KEY = "io.flutter.network-policy";

@NonNull
private static ApplicationInfo getApplicationInfo(@NonNull Context applicationContext) {
try {
return applicationContext
.getPackageManager()
.getApplicationInfo(applicationContext.getPackageName(), PackageManager.GET_META_DATA);
} catch (PackageManager.NameNotFoundException e) {
throw new RuntimeException(e);
}
}

private static String getString(Bundle metadata, String key) {
if (metadata == null) {
return null;
}
return metadata.getString(key, null);
}

private static String getNetworkPolicy(ApplicationInfo appInfo, Context context) {
// We cannot use reflection to look at networkSecurityConfigRes because
// Android throws an error when we try to access fields marked as @hide.
// Instead we rely on metadata.
Bundle metadata = appInfo.metaData;
if (metadata == null) {
return null;
}

int networkSecurityConfigRes = metadata.getInt(NETWORK_POLICY_METADATA_KEY, 0);
if (networkSecurityConfigRes <= 0) {
return null;
}

JSONArray output = new JSONArray();
try {
XmlResourceParser xrp = context.getResources().getXml(networkSecurityConfigRes);
xrp.next();
int eventType = xrp.getEventType();
while (eventType != XmlResourceParser.END_DOCUMENT) {
if (eventType == XmlResourceParser.START_TAG) {
if (xrp.getName().equals("domain-config")) {
parseDomainConfig(xrp, output, false);
}
}
eventType = xrp.next();
}
} catch (IOException | XmlPullParserException e) {
return null;
}
return output.toString();
}

private static boolean getUseEmbeddedView(ApplicationInfo appInfo) {
Bundle bundle = appInfo.metaData;
return bundle != null && bundle.getBoolean("io.flutter.embedded_views_preview");
}

private static void parseDomainConfig(
XmlResourceParser xrp, JSONArray output, boolean inheritedCleartextPermitted)
throws IOException, XmlPullParserException {
boolean cleartextTrafficPermitted =
xrp.getAttributeBooleanValue(
null, "cleartextTrafficPermitted", inheritedCleartextPermitted);
while (true) {
int eventType = xrp.next();
if (eventType == XmlResourceParser.START_TAG) {
if (xrp.getName().equals("domain")) {
// There can be multiple domains.
parseDomain(xrp, output, cleartextTrafficPermitted);
} else if (xrp.getName().equals("domain-config")) {
parseDomainConfig(xrp, output, cleartextTrafficPermitted);
} else {
skipTag(xrp);
}
} else if (eventType == XmlResourceParser.END_TAG) {
break;
}
}
}

private static void skipTag(XmlResourceParser xrp) throws IOException, XmlPullParserException {
String name = xrp.getName();
int eventType = xrp.getEventType();
while (eventType != XmlResourceParser.END_TAG || xrp.getName() != name) {
eventType = xrp.next();
}
}

private static void parseDomain(
XmlResourceParser xrp, JSONArray output, boolean cleartextPermitted)
throws IOException, XmlPullParserException {
boolean includeSubDomains = xrp.getAttributeBooleanValue(null, "includeSubdomains", false);
xrp.next();
if (xrp.getEventType() != XmlResourceParser.TEXT) {
throw new IllegalStateException("Expected text");
}
String domain = xrp.getText().trim();
JSONArray outputArray = new JSONArray();
outputArray.put(domain);
outputArray.put(includeSubDomains);
outputArray.put(cleartextPermitted);
output.put(outputArray);
xrp.next();
if (xrp.getEventType() != XmlResourceParser.END_TAG) {
throw new IllegalStateException("Expected end of domain tag");
}
}

/**
* Initialize our Flutter config values by obtaining them from the manifest XML file, falling back
* to default values.
*/
@NonNull
public static FlutterApplicationInfo load(@NonNull Context applicationContext) {
ApplicationInfo appInfo = getApplicationInfo(applicationContext);
// Prior to API 23, cleartext traffic is allowed.
boolean clearTextPermitted = true;
if (android.os.Build.VERSION.SDK_INT >= 23) {
clearTextPermitted = NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted();
}

return new FlutterApplicationInfo(
getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME),
getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY),
getString(appInfo.metaData, PUBLIC_ISOLATE_SNAPSHOT_DATA_KEY),
getString(appInfo.metaData, PUBLIC_FLUTTER_ASSETS_DIR_KEY),
getNetworkPolicy(appInfo, applicationContext),
appInfo.nativeLibraryDir,
clearTextPermitted,
getUseEmbeddedView(appInfo));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
// 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.

package io.flutter.embedding.engine.loader;

/** Encapsulates all the information that Flutter needs from application manifest. */
public final class FlutterApplicationInfo {
private static final String DEFAULT_AOT_SHARED_LIBRARY_NAME = "libapp.so";
private static final String DEFAULT_VM_SNAPSHOT_DATA = "vm_snapshot_data";
private static final String DEFAULT_ISOLATE_SNAPSHOT_DATA = "isolate_snapshot_data";
private static final String DEFAULT_FLUTTER_ASSETS_DIR = "flutter_assets";

final String aotSharedLibraryName;
final String vmSnapshotData;
final String isolateSnapshotData;
final String flutterAssetsDir;
final String domainNetworkPolicy;
final String nativeLibraryDir;
final boolean clearTextPermitted;
// TODO(cyanlaz): Remove this when dynamic thread merging is done.
// https://github.com/flutter/flutter/issues/59930
final boolean useEmbeddedView;

public FlutterApplicationInfo(
String aotSharedLibraryName,
String vmSnapshotData,
String isolateSnapshotData,
String flutterAssetsDir,
String domainNetworkPolicy,
String nativeLibraryDir,
boolean clearTextPermitted,
boolean useEmbeddedView) {
this.aotSharedLibraryName =
aotSharedLibraryName == null ? DEFAULT_AOT_SHARED_LIBRARY_NAME : aotSharedLibraryName;
this.vmSnapshotData = vmSnapshotData == null ? DEFAULT_VM_SNAPSHOT_DATA : vmSnapshotData;
this.isolateSnapshotData =
isolateSnapshotData == null ? DEFAULT_ISOLATE_SNAPSHOT_DATA : isolateSnapshotData;
this.flutterAssetsDir =
flutterAssetsDir == null ? DEFAULT_FLUTTER_ASSETS_DIR : flutterAssetsDir;
this.nativeLibraryDir = nativeLibraryDir;
this.domainNetworkPolicy = domainNetworkPolicy == null ? "" : domainNetworkPolicy;
this.clearTextPermitted = clearTextPermitted;
this.useEmbeddedView = useEmbeddedView;
}
}
Loading