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