Commit f5757b50 authored by Jason Lin's avatar Jason Lin Committed by Commit Bot

Validate sender in terminal private api browser code

See the bugs for why we are doing this.

Notes:
* `tab_id` is removed. `TerminalPrivateAckOutputFunction::Run()` used to
  use `tab_id` to figure out whether it should actually acknowledge the
  output on the process --- it should only do so if the sender owns the
  terminal id. Now that we store the terminal id in the web contents, we
  don't need it anymore.
* We change the signature of api `ackOutput`. It is not going to
  break anything because only `.../externs/terminal_private.js`, which
  is also updated in this CL, but not "libapps" is supposed to call it.
* Potentially, we should kill the renderer if the validation fails, but
  we cannot do it for now because libapps might send a invalid ID before
  it receives a valid one.
* `SetLastActiveTerminal()` is used for setting the CWD for a new
  terminal, but due to a bug, it has been effectively disabled. We don't
  want this CL to change the CWD behavior so we explicitly disable it
  here. We will soon have a follow up CL to deal with the CWD feature.
  Also see https://crbug.com/1113207.

Fixed: 1145053, 1144625
Test: new browser tests, and manual tests
Change-Id: I0f39c544a28e23df3aeefe98b626c2b94b973af7
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/2519182Reviewed-by: default avatarBen Wells <benwells@chromium.org>
Reviewed-by: default avatarJoel Hockey <joelhockey@chromium.org>
Reviewed-by: default avatarMike Frysinger <vapier@chromium.org>
Commit-Queue: Jason Lin <lxj@google.com>
Cr-Commit-Position: refs/heads/master@{#825779}
parent 175eb654
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include "ash/public/cpp/ash_pref_names.h" #include "ash/public/cpp/ash_pref_names.h"
#include "base/bind.h" #include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/containers/flat_set.h"
#include "base/json/json_writer.h" #include "base/json/json_writer.h"
#include "base/lazy_instance.h" #include "base/lazy_instance.h"
#include "base/memory/scoped_refptr.h" #include "base/memory/scoped_refptr.h"
...@@ -37,6 +38,7 @@ ...@@ -37,6 +38,7 @@
#include "content/public/browser/browser_task_traits.h" #include "content/public/browser/browser_task_traits.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h" #include "content/public/browser/web_contents.h"
#include "content/public/browser/web_contents_user_data.h"
#include "extensions/browser/app_window/app_window.h" #include "extensions/browser/app_window/app_window.h"
#include "extensions/browser/app_window/app_window_registry.h" #include "extensions/browser/app_window/app_window_registry.h"
#include "extensions/browser/event_router.h" #include "extensions/browser/event_router.h"
...@@ -75,6 +77,43 @@ const char kSwitchCurrentWorkingDir[] = "cwd"; ...@@ -75,6 +77,43 @@ const char kSwitchCurrentWorkingDir[] = "cwd";
int32_t g_last_active_pid = 0; int32_t g_last_active_pid = 0;
class TerminalTabHelper
: public content::WebContentsUserData<TerminalTabHelper> {
public:
void AddTerminalId(const std::string& terminal_id) {
if (!terminal_ids_.insert(terminal_id).second) {
LOG(ERROR) << "terminal id already exists" << terminal_id;
}
}
void RemoveTerminalId(const std::string& terminal_id) {
if (terminal_ids_.erase(terminal_id) == 0) {
LOG(ERROR) << "terminal id does not exists" << terminal_id;
}
}
static bool ValidateTerminalId(const content::WebContents* contents,
const std::string& terminal_id) {
if (contents != nullptr) {
auto* helper = TerminalTabHelper::FromWebContents(contents);
if (helper != nullptr) {
return helper->terminal_ids_.contains(terminal_id);
}
}
return false;
}
private:
explicit TerminalTabHelper(content::WebContents* contents) {}
friend class content::WebContentsUserData<TerminalTabHelper>;
WEB_CONTENTS_USER_DATA_KEY_DECL();
base::flat_set<std::string> terminal_ids_;
};
WEB_CONTENTS_USER_DATA_KEY_IMPL(TerminalTabHelper)
// Copies the value of |switch_name| if present from |src| to |dst|. If not // Copies the value of |switch_name| if present from |src| to |dst|. If not
// present, uses |default_value| if nonempty. Returns the value set into |dst|. // present, uses |default_value| if nonempty. Returns the value set into |dst|.
std::string GetSwitch(const base::CommandLine& src, std::string GetSwitch(const base::CommandLine& src,
...@@ -91,19 +130,17 @@ std::string GetSwitch(const base::CommandLine& src, ...@@ -91,19 +130,17 @@ std::string GetSwitch(const base::CommandLine& src,
} }
void NotifyProcessOutput(content::BrowserContext* browser_context, void NotifyProcessOutput(content::BrowserContext* browser_context,
int tab_id,
const std::string& terminal_id, const std::string& terminal_id,
const std::string& output_type, const std::string& output_type,
const std::string& output) { const std::string& output) {
if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) { if (!content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)) {
content::GetUIThreadTaskRunner({})->PostTask( content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&NotifyProcessOutput, browser_context, tab_id, FROM_HERE, base::BindOnce(&NotifyProcessOutput, browser_context,
terminal_id, output_type, output)); terminal_id, output_type, output));
return; return;
} }
std::unique_ptr<base::ListValue> args(new base::ListValue()); std::unique_ptr<base::ListValue> args(new base::ListValue());
args->AppendInteger(tab_id);
args->AppendString(terminal_id); args->AppendString(terminal_id);
args->AppendString(output_type); args->AppendString(output_type);
args->AppendString(output); args->AppendString(output);
...@@ -118,18 +155,6 @@ void NotifyProcessOutput(content::BrowserContext* browser_context, ...@@ -118,18 +155,6 @@ void NotifyProcessOutput(content::BrowserContext* browser_context,
} }
} }
// Returns tab ID, or window session ID (for platform apps) for |web_contents|.
int GetTabOrWindowSessionId(content::BrowserContext* browser_context,
content::WebContents* web_contents) {
int tab_id = extensions::ExtensionTabUtil::GetTabId(web_contents);
if (tab_id >= 0)
return tab_id;
extensions::AppWindow* window =
extensions::AppWindowRegistry::Get(browser_context)
->GetAppWindowForWebContents(web_contents);
return window ? window->session_id().id() : -1;
}
void PreferenceChanged(Profile* profile, void PreferenceChanged(Profile* profile,
const std::string& pref_name, const std::string& pref_name,
extensions::events::HistogramValue histogram, extensions::events::HistogramValue histogram,
...@@ -145,9 +170,7 @@ void PreferenceChanged(Profile* profile, ...@@ -145,9 +170,7 @@ void PreferenceChanged(Profile* profile,
} }
void SetLastActiveTerminal(const std::string& terminal_id) { void SetLastActiveTerminal(const std::string& terminal_id) {
// The terminal_id is <pid>-<guid>. We will parse it to get the pid. // TODO(crbug.com/1113207): disable this until we have a better cwd solution.
// atoi will read all leading digits and stop at any non-digit such as '-'.
g_last_active_pid = atoi(terminal_id.c_str());
} }
} // namespace } // namespace
...@@ -209,35 +232,21 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess( ...@@ -209,35 +232,21 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess(
if (!caller_contents) if (!caller_contents)
return RespondNow(Error("No web contents.")); return RespondNow(Error("No web contents."));
// Passed to terminalPrivate.ackOutput, which is called from the API's custom
// bindings after terminalPrivate.onProcessOutput is dispatched. It is used to
// determine whether ackOutput call should be handled or not. ackOutput will
// be called from every web contents in which a onProcessOutput listener
// exists (because the API custom bindings hooks are run in every web contents
// with a listener). Only ackOutput called from the web contents that has the
// target terminal instance should be handled.
// TODO(tbarzic): Instead of passing tab/app window session id around, keep
// mapping from web_contents to terminal ID running in it. This will be
// needed to fix crbug.com/210295.
int tab_id = GetTabOrWindowSessionId(browser_context(), caller_contents);
if (tab_id < 0)
return RespondNow(Error("Not called from a tab or app window"));
// Passing --crosh-command overrides any JS process name. // Passing --crosh-command overrides any JS process name.
base::CommandLine* command_line = base::CommandLine::ForCurrentProcess(); base::CommandLine* command_line = base::CommandLine::ForCurrentProcess();
if (command_line->HasSwitch(switches::kCroshCommand)) { if (command_line->HasSwitch(switches::kCroshCommand)) {
OpenProcess( OpenProcess(
user_id_hash, tab_id, user_id_hash,
base::CommandLine(base::FilePath( base::CommandLine(base::FilePath(
command_line->GetSwitchValueASCII(switches::kCroshCommand)))); command_line->GetSwitchValueASCII(switches::kCroshCommand))));
} else if (process_name == kCroshName) { } else if (process_name == kCroshName) {
// command=crosh: use '/usr/bin/crosh' on a device, 'cat' otherwise. // command=crosh: use '/usr/bin/crosh' on a device, 'cat' otherwise.
if (base::SysInfo::IsRunningOnChromeOS()) { if (base::SysInfo::IsRunningOnChromeOS()) {
OpenProcess(user_id_hash, tab_id, OpenProcess(user_id_hash,
base::CommandLine(base::FilePath(kCroshCommand))); base::CommandLine(base::FilePath(kCroshCommand)));
} else { } else {
OpenProcess(user_id_hash, tab_id, OpenProcess(user_id_hash,
base::CommandLine(base::FilePath(kStubbedCroshCommand))); base::CommandLine(base::FilePath(kStubbedCroshCommand)));
} }
...@@ -268,8 +277,7 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess( ...@@ -268,8 +277,7 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess(
auto* mgr = crostini::CrostiniManager::GetForProfile(profile); auto* mgr = crostini::CrostiniManager::GetForProfile(profile);
bool verbose = !mgr->GetContainerInfo(container_id).has_value(); bool verbose = !mgr->GetContainerInfo(container_id).has_value();
auto observer = std::make_unique<CrostiniStartupStatus>( auto observer = std::make_unique<CrostiniStartupStatus>(
base::BindRepeating(&NotifyProcessOutput, browser_context(), tab_id, base::BindRepeating(&NotifyProcessOutput, browser_context(), startup_id,
startup_id,
api::terminal_private::ToString( api::terminal_private::ToString(
api::terminal_private::OUTPUT_TYPE_STDOUT)), api::terminal_private::OUTPUT_TYPE_STDOUT)),
verbose); verbose);
...@@ -280,8 +288,7 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess( ...@@ -280,8 +288,7 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess(
container_id, container_id,
base::BindOnce( base::BindOnce(
&TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted, &TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted,
this, std::move(observer), user_id_hash, tab_id, this, std::move(observer), user_id_hash, std::move(cmdline)),
std::move(cmdline)),
observer_ptr); observer_ptr);
} else { } else {
// command=[unrecognized]. // command=[unrecognized].
...@@ -293,7 +300,6 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess( ...@@ -293,7 +300,6 @@ TerminalPrivateOpenTerminalProcessFunction::OpenProcess(
void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted( void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted(
std::unique_ptr<CrostiniStartupStatus> startup_status, std::unique_ptr<CrostiniStartupStatus> startup_status,
const std::string& user_id_hash, const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline, base::CommandLine cmdline,
crostini::CrostiniResult result) { crostini::CrostiniResult result) {
if (crostini::MaybeShowCrostiniDialogBeforeLaunch( if (crostini::MaybeShowCrostiniDialogBeforeLaunch(
...@@ -305,7 +311,7 @@ void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted( ...@@ -305,7 +311,7 @@ void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted(
} }
startup_status->OnCrostiniRestarted(result); startup_status->OnCrostiniRestarted(result);
if (result == crostini::CrostiniResult::SUCCESS) { if (result == crostini::CrostiniResult::SUCCESS) {
OpenVmshellProcess(user_id_hash, tab_id, std::move(cmdline)); OpenVmshellProcess(user_id_hash, std::move(cmdline));
} else { } else {
const std::string msg = const std::string msg =
base::StringPrintf("Error starting crostini for terminal: %d", result); base::StringPrintf("Error starting crostini for terminal: %d", result);
...@@ -316,11 +322,10 @@ void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted( ...@@ -316,11 +322,10 @@ void TerminalPrivateOpenTerminalProcessFunction::OnCrostiniRestarted(
void TerminalPrivateOpenTerminalProcessFunction::OpenVmshellProcess( void TerminalPrivateOpenTerminalProcessFunction::OpenVmshellProcess(
const std::string& user_id_hash, const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline) { base::CommandLine cmdline) {
// If cwd is already set in cmdline, or this is the first terminal, open now. // If cwd is already set in cmdline, or this is the first terminal, open now.
if (cmdline.HasSwitch(kSwitchCurrentWorkingDir) || !g_last_active_pid) { if (cmdline.HasSwitch(kSwitchCurrentWorkingDir) || !g_last_active_pid) {
return OpenProcess(user_id_hash, tab_id, std::move(cmdline)); return OpenProcess(user_id_hash, std::move(cmdline));
} }
// Lookup container shell pid from cicierone to use for cwd. // Lookup container shell pid from cicierone to use for cwd.
...@@ -330,13 +335,11 @@ void TerminalPrivateOpenTerminalProcessFunction::OpenVmshellProcess( ...@@ -330,13 +335,11 @@ void TerminalPrivateOpenTerminalProcessFunction::OpenVmshellProcess(
crostini::ContainerId::GetDefault(), g_last_active_pid, crostini::ContainerId::GetDefault(), g_last_active_pid,
base::BindOnce( base::BindOnce(
&TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession, &TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession,
this, user_id_hash, tab_id, std::move(cmdline), this, user_id_hash, std::move(cmdline), g_last_active_pid));
g_last_active_pid));
} }
void TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession( void TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession(
const std::string& user_id_hash, const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline, base::CommandLine cmdline,
int32_t vsh_pid, int32_t vsh_pid,
bool success, bool success,
...@@ -349,12 +352,11 @@ void TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession( ...@@ -349,12 +352,11 @@ void TerminalPrivateOpenTerminalProcessFunction::OnGetVshSession(
cmdline.AppendSwitchASCII(kSwitchCurrentWorkingDir, cmdline.AppendSwitchASCII(kSwitchCurrentWorkingDir,
base::NumberToString(container_shell_pid)); base::NumberToString(container_shell_pid));
} }
OpenProcess(user_id_hash, tab_id, std::move(cmdline)); OpenProcess(user_id_hash, std::move(cmdline));
} }
void TerminalPrivateOpenTerminalProcessFunction::OpenProcess( void TerminalPrivateOpenTerminalProcessFunction::OpenProcess(
const std::string& user_id_hash, const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline) { base::CommandLine cmdline) {
DCHECK(!cmdline.argv().empty()); DCHECK(!cmdline.argv().empty());
// Registry lives on its own task runner. // Registry lives on its own task runner.
...@@ -362,7 +364,7 @@ void TerminalPrivateOpenTerminalProcessFunction::OpenProcess( ...@@ -362,7 +364,7 @@ void TerminalPrivateOpenTerminalProcessFunction::OpenProcess(
FROM_HERE, FROM_HERE,
base::BindOnce( base::BindOnce(
&TerminalPrivateOpenTerminalProcessFunction::OpenOnRegistryTaskRunner, &TerminalPrivateOpenTerminalProcessFunction::OpenOnRegistryTaskRunner,
this, base::Bind(&NotifyProcessOutput, browser_context(), tab_id), this, base::Bind(&NotifyProcessOutput, browser_context()),
base::Bind( base::Bind(
&TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread, &TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread,
this), this),
...@@ -387,12 +389,31 @@ void TerminalPrivateOpenTerminalProcessFunction::OpenOnRegistryTaskRunner( ...@@ -387,12 +389,31 @@ void TerminalPrivateOpenTerminalProcessFunction::OpenOnRegistryTaskRunner(
void TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread( void TerminalPrivateOpenTerminalProcessFunction::RespondOnUIThread(
bool success, bool success,
const std::string& terminal_id) { const std::string& terminal_id) {
auto* contents = GetSenderWebContents();
if (!contents) {
LOG(WARNING) << "content is closed before returning opened process";
chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](const std::string& terminal_id) {
if (!chromeos::ProcessProxyRegistry::Get()->CloseProcess(
terminal_id)) {
LOG(ERROR) << "Unable to close terminal " << terminal_id;
}
},
terminal_id));
return;
}
if (!success) { if (!success) {
Respond(Error("Failed to open process.")); Respond(Error("Failed to open process."));
return; return;
} }
SetLastActiveTerminal(terminal_id); SetLastActiveTerminal(terminal_id);
Respond(OneArgument(base::Value(terminal_id))); Respond(OneArgument(base::Value(terminal_id)));
TerminalTabHelper::CreateForWebContents(contents);
TerminalTabHelper::FromWebContents(contents)->AddTerminalId(terminal_id);
} }
TerminalPrivateOpenVmshellProcessFunction:: TerminalPrivateOpenVmshellProcessFunction::
...@@ -413,6 +434,13 @@ TerminalPrivateSendInputFunction::~TerminalPrivateSendInputFunction() = default; ...@@ -413,6 +434,13 @@ TerminalPrivateSendInputFunction::~TerminalPrivateSendInputFunction() = default;
ExtensionFunction::ResponseAction TerminalPrivateSendInputFunction::Run() { ExtensionFunction::ResponseAction TerminalPrivateSendInputFunction::Run() {
std::unique_ptr<SendInput::Params> params(SendInput::Params::Create(*args_)); std::unique_ptr<SendInput::Params> params(SendInput::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get()); EXTENSION_FUNCTION_VALIDATE(params.get());
if (!TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(),
params->id)) {
LOG(ERROR) << "invalid terminal id " << params->id;
return RespondNow(Error("invalid terminal id"));
}
SetLastActiveTerminal(params->id); SetLastActiveTerminal(params->id);
// Registry lives on its own task runner. // Registry lives on its own task runner.
...@@ -449,6 +477,12 @@ TerminalPrivateCloseTerminalProcessFunction::Run() { ...@@ -449,6 +477,12 @@ TerminalPrivateCloseTerminalProcessFunction::Run() {
CloseTerminalProcess::Params::Create(*args_)); CloseTerminalProcess::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get()); EXTENSION_FUNCTION_VALIDATE(params.get());
if (!TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(),
params->id)) {
LOG(ERROR) << "invalid terminal id " << params->id;
return RespondNow(Error("invalid terminal id"));
}
// Registry lives on its own task runner. // Registry lives on its own task runner.
chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask( chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE, base::BindOnce(&TerminalPrivateCloseTerminalProcessFunction:: FROM_HERE, base::BindOnce(&TerminalPrivateCloseTerminalProcessFunction::
...@@ -483,6 +517,13 @@ TerminalPrivateOnTerminalResizeFunction::Run() { ...@@ -483,6 +517,13 @@ TerminalPrivateOnTerminalResizeFunction::Run() {
std::unique_ptr<OnTerminalResize::Params> params( std::unique_ptr<OnTerminalResize::Params> params(
OnTerminalResize::Params::Create(*args_)); OnTerminalResize::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get()); EXTENSION_FUNCTION_VALIDATE(params.get());
if (!TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(),
params->id)) {
LOG(ERROR) << "invalid terminal id " << params->id;
return RespondNow(Error("invalid terminal id"));
}
SetLastActiveTerminal(params->id); SetLastActiveTerminal(params->id);
// Registry lives on its own task runner. // Registry lives on its own task runner.
...@@ -519,23 +560,17 @@ ExtensionFunction::ResponseAction TerminalPrivateAckOutputFunction::Run() { ...@@ -519,23 +560,17 @@ ExtensionFunction::ResponseAction TerminalPrivateAckOutputFunction::Run() {
std::unique_ptr<AckOutput::Params> params(AckOutput::Params::Create(*args_)); std::unique_ptr<AckOutput::Params> params(AckOutput::Params::Create(*args_));
EXTENSION_FUNCTION_VALIDATE(params.get()); EXTENSION_FUNCTION_VALIDATE(params.get());
content::WebContents* caller_contents = GetSenderWebContents(); // Every running terminal page will call ackOutput(), but we should only react
if (!caller_contents) // for the one who actually owns the output.
return RespondNow(Error("No web contents.")); if (TerminalTabHelper::ValidateTerminalId(GetSenderWebContents(),
params->id)) {
int tab_id = GetTabOrWindowSessionId(browser_context(), caller_contents); // Registry lives on its own task runner.
if (tab_id < 0) chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask(
return RespondNow(Error("Not called from a tab or app window")); FROM_HERE,
base::BindOnce(
if (tab_id != params->tab_id) &TerminalPrivateAckOutputFunction::AckOutputOnRegistryTaskRunner,
return RespondNow(NoArguments()); this, params->id));
}
// Registry lives on its own task runner.
chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
&TerminalPrivateAckOutputFunction::AckOutputOnRegistryTaskRunner,
this, params->id));
return RespondNow(NoArguments()); return RespondNow(NoArguments());
} }
......
...@@ -62,16 +62,13 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction { ...@@ -62,16 +62,13 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction {
void OnCrostiniRestarted( void OnCrostiniRestarted(
std::unique_ptr<CrostiniStartupStatus> startup_status, std::unique_ptr<CrostiniStartupStatus> startup_status,
const std::string& user_id_hash, const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline, base::CommandLine cmdline,
crostini::CrostiniResult result); crostini::CrostiniResult result);
void OpenVmshellProcess(const std::string& user_id_hash, void OpenVmshellProcess(const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline); base::CommandLine cmdline);
void OnGetVshSession(const std::string& user_id_hash, void OnGetVshSession(const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline, base::CommandLine cmdline,
int32_t vsh_pid, int32_t vsh_pid,
bool success, bool success,
...@@ -79,7 +76,6 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction { ...@@ -79,7 +76,6 @@ class TerminalPrivateOpenTerminalProcessFunction : public ExtensionFunction {
int32_t container_shell_pid); int32_t container_shell_pid);
void OpenProcess(const std::string& user_id_hash, void OpenProcess(const std::string& user_id_hash,
int tab_id,
base::CommandLine cmdline); base::CommandLine cmdline);
using ProcessOutputCallback = using ProcessOutputCallback =
......
...@@ -2,11 +2,100 @@ ...@@ -2,11 +2,100 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
#include "base/bind.h"
#include "base/command_line.h" #include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/location.h"
#include "base/run_loop.h"
#include "base/test/bind.h"
#include "chrome/browser/extensions/extension_apitest.h" #include "chrome/browser/extensions/extension_apitest.h"
#include "chromeos/process_proxy/process_proxy_registry.h"
#include "content/public/browser/browser_task_traits.h"
#include "content/public/test/browser_test.h" #include "content/public/test/browser_test.h"
#include "extensions/common/switches.h" #include "extensions/common/switches.h"
namespace {
// For running and maintaining a `cat` process using `ProcessProxyRegistry`
// directly.
class CatProcess {
public:
CatProcess() {
base::RunLoop run_loop;
chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
chromeos::ProcessProxyRegistry* registry =
chromeos::ProcessProxyRegistry::Get();
auto on_output_on_process_thread = base::BindLambdaForTesting(
[this](const std::string&, const std::string&,
const std::string&) {
content::GetUIThreadTaskRunner({})->PostTask(
FROM_HERE, base::BindOnce(&CatProcess::OnOutputOnUIThread,
base::Unretained(this)));
});
// `user_id_hash` does not seems to matter in test.
this->ok_ = registry->OpenProcess(
base::CommandLine(base::FilePath("cat")), /*user_id_hash=*/"user",
on_output_on_process_thread, &this->process_id_);
run_loop.Quit();
}));
run_loop.Run();
}
~CatProcess() {
if (ok_) {
chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE,
base::BindOnce(
[](const std::string& process_id) {
chromeos::ProcessProxyRegistry::Get()->CloseProcess(process_id);
},
process_id_));
}
}
bool ok() const { return ok_; }
bool has_output() const { return has_output_; }
const std::string& process_id() const { return process_id_; }
void send_input_and_wait_output(const std::string& data) {
base::RunLoop run_loop;
CHECK(on_output_closure_.is_null());
on_output_closure_ = run_loop.QuitClosure();
chromeos::ProcessProxyRegistry::GetTaskRunner()->PostTask(
FROM_HERE, base::BindLambdaForTesting([&]() {
chromeos::ProcessProxyRegistry::Get()->SendInput(this->process_id_,
data);
}));
run_loop.Run();
}
private:
void OnOutputOnUIThread() {
// We don't bother to call `ProcessProxyRegistry::AckOutput()` here since we
// only need to know whether there is some output.
has_output_ = true;
if (!on_output_closure_.is_null()) {
std::move(on_output_closure_).Run();
}
}
bool ok_;
std::string process_id_;
bool has_output_{false};
base::OnceClosure on_output_closure_;
};
} // namespace
class ExtensionTerminalPrivateApiTest : public extensions::ExtensionApiTest { class ExtensionTerminalPrivateApiTest : public extensions::ExtensionApiTest {
void SetUpCommandLine(base::CommandLine* command_line) override { void SetUpCommandLine(base::CommandLine* command_line) override {
extensions::ExtensionApiTest::SetUpCommandLine(command_line); extensions::ExtensionApiTest::SetUpCommandLine(command_line);
...@@ -16,7 +105,25 @@ class ExtensionTerminalPrivateApiTest : public extensions::ExtensionApiTest { ...@@ -16,7 +105,25 @@ class ExtensionTerminalPrivateApiTest : public extensions::ExtensionApiTest {
} }
}; };
IN_PROC_BROWSER_TEST_F(ExtensionTerminalPrivateApiTest, CatProcess) {
CatProcess cat_process;
ASSERT_TRUE(cat_process.ok());
ASSERT_FALSE(cat_process.has_output());
cat_process.send_input_and_wait_output("hello");
ASSERT_TRUE(cat_process.has_output());
}
IN_PROC_BROWSER_TEST_F(ExtensionTerminalPrivateApiTest, TerminalTest) { IN_PROC_BROWSER_TEST_F(ExtensionTerminalPrivateApiTest, TerminalTest) {
EXPECT_TRUE(RunExtensionSubtest("terminal/component_extension", "test.html")) CatProcess cat_process;
ASSERT_TRUE(cat_process.ok());
EXPECT_TRUE(
RunExtensionSubtest("terminal/component_extension",
"test.html?foreign_id=" + cat_process.process_id()))
<< message_; << message_;
// Double check that test.html cannot write to the cat process here;
// Otherwises, we should detect some output.
base::RunLoop().RunUntilIdle();
EXPECT_FALSE(cat_process.has_output());
} }
...@@ -173,11 +173,6 @@ ...@@ -173,11 +173,6 @@
"type": "function", "type": "function",
"description": "Called from |onProcessOutput| when the event is dispatched to terminal extension. Observing the terminal process output will be paused after |onProcessOutput| is dispatched until this method is called.", "description": "Called from |onProcessOutput| when the event is dispatched to terminal extension. Observing the terminal process output will be paused after |onProcessOutput| is dispatched until this method is called.",
"parameters": [ "parameters": [
{
"name": "tabId",
"type": "integer",
"description": "Tab ID from |onProcessOutput| event."
},
{ {
"name": "id", "name": "id",
"type": "string", "type": "string",
......
...@@ -3,14 +3,12 @@ ...@@ -3,14 +3,12 @@
// found in the LICENSE file. // found in the LICENSE file.
// Custom bindings for chrome.terminalPrivate API. // Custom bindings for chrome.terminalPrivate API.
bindingUtil.registerEventArgumentMassager('terminalPrivate.onProcessOutput', bindingUtil.registerEventArgumentMassager(
function(args, dispatch) { 'terminalPrivate.onProcessOutput', function(args, dispatch) {
var tabId = args[0]; const terminalId = args[0];
var terminalId = args[1]; try {
try { dispatch(args);
// Remove tabId from event args, as it's not expected by listeners. } finally {
dispatch(args.slice(1)); chrome.terminalPrivate.ackOutput(terminalId);
} finally { }
chrome.terminalPrivate.ackOutput(tabId, terminalId); });
}
});
...@@ -288,4 +288,23 @@ chrome.test.runTests([ ...@@ -288,4 +288,23 @@ chrome.test.runTests([
}); });
}, },
function invalidTerminalIdTest() {
const foreign_id = (new URLSearchParams(location.search)).get('foreign_id');
chrome.test.assertTrue(!!foreign_id);
const callbackFail = chrome.test.callbackFail;
[foreign_id, 'invalid id'].forEach((id) => {
// Ideally, we will also want to test ackOutput, but it does not have a
// result callback.
chrome.terminalPrivate.closeTerminalProcess(
id, callbackFail('invalid terminal id'));
// If this manages to write to the `foreign_id` process, we should detect
// some output in terminal_private_apitest.cc.
chrome.terminalPrivate.sendInput(
id, 'hello', callbackFail('invalid terminal id'));
chrome.terminalPrivate.onTerminalResize(
id, 10, 10, callbackFail('invalid terminal id'));
});
},
]); ]);
...@@ -77,11 +77,10 @@ chrome.terminalPrivate.onTerminalResize = function(id, width, height, callback) ...@@ -77,11 +77,10 @@ chrome.terminalPrivate.onTerminalResize = function(id, width, height, callback)
* Called from |onProcessOutput| when the event is dispatched to terminal * Called from |onProcessOutput| when the event is dispatched to terminal
* extension. Observing the terminal process output will be paused after * extension. Observing the terminal process output will be paused after
* |onProcessOutput| is dispatched until this method is called. * |onProcessOutput| is dispatched until this method is called.
* @param {number} tabId Tab ID from |onProcessOutput| event.
* @param {string} id The id of the process to which |onProcessOutput| was * @param {string} id The id of the process to which |onProcessOutput| was
* dispatched. * dispatched.
*/ */
chrome.terminalPrivate.ackOutput = function(tabId, id) {}; chrome.terminalPrivate.ackOutput = function(id) {};
/** /**
* Open the Terminal tabbed window. * Open the Terminal tabbed window.
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment