1 // Copyright 2019 The Chromium 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 #include "chrome/browser/extensions/api/messaging/native_messaging_launch_from_native.h"
6
7 #include <memory>
8 #include <utility>
9
10 #include "base/bind.h"
11 #include "base/callback_helpers.h"
12 #include "base/strings/strcat.h"
13 #include "base/strings/string_util.h"
14 #include "base/threading/sequenced_task_runner_handle.h"
15 #include "base/timer/timer.h"
16 #include "chrome/browser/browser_process.h"
17 #include "chrome/browser/extensions/api/messaging/native_message_port.h"
18 #include "chrome/browser/extensions/api/messaging/native_message_process_host.h"
19 #include "chrome/browser/extensions/api/messaging/native_process_launcher.h"
20 #include "chrome/browser/profiles/profile.h"
21 #include "chrome/common/extensions/manifest_handlers/natively_connectable_handler.h"
22 #include "components/keep_alive_registry/keep_alive_types.h"
23 #include "components/keep_alive_registry/scoped_keep_alive.h"
24 #include "content/public/browser/browser_task_traits.h"
25 #include "content/public/browser/browser_thread.h"
26 #include "extensions/browser/api/messaging/channel_endpoint.h"
27 #include "extensions/browser/api/messaging/message_service.h"
28 #include "extensions/browser/api/messaging/native_message_host.h"
29 #include "extensions/browser/event_router.h"
30 #include "extensions/browser/extension_registry.h"
31 #include "extensions/common/api/messaging/messaging_endpoint.h"
32 #include "extensions/common/permissions/permission_set.h"
33 #include "extensions/common/permissions/permissions_data.h"
34
35 namespace extensions {
36 namespace {
37
38 ScopedAllowNativeAppConnectionForTest* g_allow_native_app_connection_for_test =
39 nullptr;
40
41 constexpr base::TimeDelta kNativeMessagingHostErrorTimeout =
42 base::TimeDelta::FromSeconds(10);
43
44 ScopedNativeMessagingErrorTimeoutOverrideForTest*
45 g_native_messaging_host_timeout_override = nullptr;
46
47 // A self-owning class responsible for starting a native messaging host with
48 // command-line parameters reporting an error, keeping Chrome alive until the
49 // host terminates, or a timeout is reached.
50 //
51 // This lives on the IO thread, but its public factory static method should be
52 // called on the UI thread.
53 class NativeMessagingHostErrorReporter : public NativeMessageHost::Client {
54 public:
55 using MovableScopedKeepAlive =
56 std::unique_ptr<ScopedKeepAlive,
57 content::BrowserThread::DeleteOnUIThread>;
58
Report(const std::string & extension_id,const std::string & host_id,const std::string & connection_id,Profile * profile,const std::string & error_arg)59 static void Report(const std::string& extension_id,
60 const std::string& host_id,
61 const std::string& connection_id,
62 Profile* profile,
63 const std::string& error_arg) {
64 DCHECK_CURRENTLY_ON(content::BrowserThread::UI);
65
66 std::unique_ptr<NativeMessageHost> host =
67 NativeMessageProcessHost::CreateWithLauncher(
68 extension_id, host_id,
69 NativeProcessLauncher::CreateDefault(
70 /* allow_user_level = */ true,
71 /* native_view = */ nullptr, profile->GetPath(),
72 /* require_native_initiated_connections = */ false,
73 connection_id, error_arg));
74 MovableScopedKeepAlive keep_alive(
75 new ScopedKeepAlive(KeepAliveOrigin::NATIVE_MESSAGING_HOST_ERROR_REPORT,
76 KeepAliveRestartOption::DISABLED));
77 content::GetIOThreadTaskRunner({})->PostTask(
78 FROM_HERE,
79 base::BindOnce(&NativeMessagingHostErrorReporter::ReportOnIoThread,
80 std::move(host), std::move(keep_alive)));
81 }
82
83 private:
ReportOnIoThread(std::unique_ptr<NativeMessageHost> process,MovableScopedKeepAlive keep_alive)84 static void ReportOnIoThread(std::unique_ptr<NativeMessageHost> process,
85 MovableScopedKeepAlive keep_alive) {
86 new NativeMessagingHostErrorReporter(std::move(process),
87 std::move(keep_alive));
88 }
89
NativeMessagingHostErrorReporter(std::unique_ptr<NativeMessageHost> process,MovableScopedKeepAlive keep_alive)90 NativeMessagingHostErrorReporter(std::unique_ptr<NativeMessageHost> process,
91 MovableScopedKeepAlive keep_alive)
92 : keep_alive_(std::move(keep_alive)), process_(std::move(process)) {
93 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
94 timeout_.Start(
95 FROM_HERE,
96 g_native_messaging_host_timeout_override
97 ? g_native_messaging_host_timeout_override->timeout()
98 : kNativeMessagingHostErrorTimeout,
99 base::BindOnce(&base::DeletePointer<NativeMessagingHostErrorReporter>,
100 base::Unretained(this)));
101 process_->Start(this);
102 }
103
104 private:
105 // NativeMessageHost::Client:
PostMessageFromNativeHost(const std::string & message)106 void PostMessageFromNativeHost(const std::string& message) override {}
107
CloseChannel(const std::string & error_message)108 void CloseChannel(const std::string& error_message) override {
109 DCHECK_CURRENTLY_ON(content::BrowserThread::IO);
110
111 timeout_.AbandonAndStop();
112
113 base::SequencedTaskRunnerHandle::Get()->DeleteSoon(FROM_HERE, this);
114 }
115
116 MovableScopedKeepAlive keep_alive_;
117 std::unique_ptr<NativeMessageHost> process_;
118 base::OneShotTimer timeout_;
119
120 DISALLOW_COPY_AND_ASSIGN(NativeMessagingHostErrorReporter);
121 };
122
123 } // namespace
124
ExtensionSupportsConnectionFromNativeApp(const std::string & extension_id,const std::string & host_id,Profile * profile,bool log_errors)125 bool ExtensionSupportsConnectionFromNativeApp(const std::string& extension_id,
126 const std::string& host_id,
127 Profile* profile,
128 bool log_errors) {
129 if (g_allow_native_app_connection_for_test) {
130 return g_allow_native_app_connection_for_test->allow();
131 }
132 if (profile->IsOffTheRecord()) {
133 return false;
134 }
135 auto* extension =
136 ExtensionRegistry::Get(profile)->enabled_extensions().GetByID(
137 extension_id);
138 if (!extension) {
139 LOG_IF(ERROR, log_errors)
140 << "Failed to launch native messaging connection: Unknown extension ID "
141 << extension_id;
142 return false;
143 }
144 const auto* natively_connectable_hosts =
145 NativelyConnectableHosts::GetConnectableNativeMessageHosts(*extension);
146 if (!natively_connectable_hosts ||
147 !natively_connectable_hosts->count(host_id)) {
148 LOG_IF(ERROR, log_errors)
149 << "Extension \"" << extension_id << "\" does not list \"" << host_id
150 << "\" in its natively_connectable manifest field";
151 return false;
152 }
153 if (!extension->permissions_data()->active_permissions().HasAPIPermission(
154 "nativeMessaging")) {
155 LOG_IF(ERROR, log_errors)
156 << "Extension \"" << extension_id
157 << "\" does not have the \"nativeMessaging\" permission";
158 return false;
159 }
160 if (!extension->permissions_data()->active_permissions().HasAPIPermission(
161 "transientBackground")) {
162 LOG_IF(ERROR, log_errors)
163 << "Extension \"" << extension_id
164 << "\" does not have the \"transientBackground\" permission";
165 return false;
166 }
167 if (!EventRouter::Get(profile)->ExtensionHasEventListener(
168 extension_id, "runtime.onConnectNative")) {
169 LOG_IF(ERROR, log_errors)
170 << "Failed to launch native messaging connection: Extension \""
171 << extension_id << "\" is not listening for runtime.onConnectNative";
172 return false;
173 }
174
175 return true;
176 }
177
ScopedAllowNativeAppConnectionForTest(bool allow)178 ScopedAllowNativeAppConnectionForTest::ScopedAllowNativeAppConnectionForTest(
179 bool allow)
180 : allow_(allow) {
181 DCHECK(!g_allow_native_app_connection_for_test);
182 g_allow_native_app_connection_for_test = this;
183 }
184
185 ScopedAllowNativeAppConnectionForTest::
~ScopedAllowNativeAppConnectionForTest()186 ~ScopedAllowNativeAppConnectionForTest() {
187 DCHECK_EQ(this, g_allow_native_app_connection_for_test);
188 g_allow_native_app_connection_for_test = nullptr;
189 }
190
191 ScopedNativeMessagingErrorTimeoutOverrideForTest::
ScopedNativeMessagingErrorTimeoutOverrideForTest(base::TimeDelta timeout)192 ScopedNativeMessagingErrorTimeoutOverrideForTest(base::TimeDelta timeout)
193 : timeout_(timeout) {
194 DCHECK(!g_native_messaging_host_timeout_override);
195 g_native_messaging_host_timeout_override = this;
196 }
197
198 ScopedNativeMessagingErrorTimeoutOverrideForTest::
~ScopedNativeMessagingErrorTimeoutOverrideForTest()199 ~ScopedNativeMessagingErrorTimeoutOverrideForTest() {
200 DCHECK_EQ(this, g_native_messaging_host_timeout_override);
201 g_native_messaging_host_timeout_override = nullptr;
202 }
203
IsValidConnectionId(const base::StringPiece connection_id)204 bool IsValidConnectionId(const base::StringPiece connection_id) {
205 return connection_id.size() <= 20 &&
206 base::ContainsOnlyChars(
207 connection_id,
208 "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ-");
209 }
210
LaunchNativeMessageHostFromNativeApp(const std::string & extension_id,const std::string & host_id,const std::string & connection_id,Profile * profile)211 void LaunchNativeMessageHostFromNativeApp(const std::string& extension_id,
212 const std::string& host_id,
213 const std::string& connection_id,
214 Profile* profile) {
215 if (!IsValidConnectionId(connection_id)) {
216 NativeMessagingHostErrorReporter::Report(extension_id, host_id,
217 /* connect_id = */ {}, profile,
218 "--invalid-connect-id");
219 return;
220 }
221 if (!ExtensionSupportsConnectionFromNativeApp(extension_id, host_id, profile,
222 /* log_errors = */ true)) {
223 NativeMessagingHostErrorReporter::Report(extension_id, host_id,
224 connection_id, profile,
225 "--extension-not-installed");
226 return;
227 }
228 const extensions::PortId port_id(base::UnguessableToken::Create(),
229 1 /* port_number */, true /* is_opener */);
230 extensions::MessageService* const message_service =
231 extensions::MessageService::Get(profile);
232 // TODO(crbug.com/967262): Apply policy for allow_user_level.
233 auto native_message_host = NativeMessageProcessHost::CreateWithLauncher(
234 extension_id, host_id,
235 NativeProcessLauncher::CreateDefault(
236 /* allow_user_level = */ true, /* native_view = */ nullptr,
237 profile->GetPath(),
238 /* require_native_initiated_connections = */ true, connection_id,
239 ""));
240 auto native_message_port = std::make_unique<extensions::NativeMessagePort>(
241 message_service->GetChannelDelegate(), port_id,
242 std::move(native_message_host));
243 message_service->OpenChannelToExtension(
244 extensions::ChannelEndpoint(profile), port_id,
245 extensions::MessagingEndpoint::ForNativeApp(host_id),
246 std::move(native_message_port), extension_id, GURL(),
247 std::string() /* channel_name */);
248 }
249
250 } // namespace extensions
251