
Addresses ~14% of `-WUnsafe-buffer-usage` opt-out in `//content`. This is a #cleanup. The patch was initially partially generated by `./tool/clang/spanify`. This patch applies conversions specifically to c-arrays in `//content`. Although the `spanify` tool supports broader conversions, this change intentionally focuses on c-arrays. Several manual fixes were made to address anonymous struct issues and other edge cases encountered during the conversion process. Bug: 342213636, 40285824 Change-Id: I72666faa45a461ea27bb3608beeacc6c9fcd8a63 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5824992 Reviewed-by: Nasko Oskov <nasko@chromium.org> Commit-Queue: Arthur Sonzogni <arthursonzogni@chromium.org> Cr-Commit-Position: refs/heads/main@{#1349804}
327 lines
11 KiB
C++
327 lines
11 KiB
C++
// Copyright 2019 The Chromium Authors
|
|
// Use of this source code is governed by a BSD-style license that can be
|
|
// found in the LICENSE file.
|
|
|
|
#include "content/public/browser/service_process_host.h"
|
|
|
|
#include <string.h>
|
|
|
|
#include <array>
|
|
|
|
#include "base/memory/shared_memory_mapping.h"
|
|
#include "base/memory/unsafe_shared_memory_region.h"
|
|
#include "base/process/process.h"
|
|
#include "base/run_loop.h"
|
|
#include "base/test/bind.h"
|
|
#include "base/time/time.h"
|
|
#include "base/timer/elapsed_timer.h"
|
|
#include "content/public/test/browser_test.h"
|
|
#include "content/public/test/content_browser_test.h"
|
|
#include "services/test/echo/public/mojom/echo.mojom.h"
|
|
#include "testing/gtest/include/gtest/gtest.h"
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
#include <windows.h>
|
|
|
|
#include "base/base_paths.h"
|
|
#include "base/files/file_path.h"
|
|
#include "base/path_service.h"
|
|
#include "content/public/browser/service_process_host_passkeys.h"
|
|
#endif
|
|
|
|
namespace content {
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
using LoadStatus = echo::mojom::EchoService::LoadStatus;
|
|
namespace {
|
|
// This is used as the module name to load, and to make the DLL filename.
|
|
constexpr const wchar_t* kEchoPreloadLibrary = L"echo_preload_library";
|
|
|
|
// DLL path relative to current executable.
|
|
base::FilePath GetDllPath(std::wstring mod_name) {
|
|
mod_name.append(L".dll");
|
|
auto exe_dir = base::PathService::CheckedGet(base::BasePathKey::DIR_EXE);
|
|
return exe_dir.Append(mod_name);
|
|
}
|
|
} // namespace
|
|
#endif // BUILDFLAG(IS_WIN)
|
|
|
|
constexpr char kTestUrl[] = "https://foo.bar";
|
|
|
|
using ServiceProcessHostBrowserTest = ContentBrowserTest;
|
|
|
|
class EchoServiceProcessObserver : public ServiceProcessHost::Observer {
|
|
public:
|
|
EchoServiceProcessObserver() { ServiceProcessHost::AddObserver(this); }
|
|
|
|
EchoServiceProcessObserver(const EchoServiceProcessObserver&) = delete;
|
|
EchoServiceProcessObserver& operator=(const EchoServiceProcessObserver&) =
|
|
delete;
|
|
|
|
~EchoServiceProcessObserver() override {
|
|
ServiceProcessHost::RemoveObserver(this);
|
|
}
|
|
|
|
void WaitForLaunch() { launch_loop_.Run(); }
|
|
void WaitForDeath() { death_loop_.Run(); }
|
|
void WaitForCrash() { crash_loop_.Run(); }
|
|
|
|
// Valid after WaitForLaunch.
|
|
base::ProcessId pid() const { return process_.Pid(); }
|
|
|
|
private:
|
|
// ServiceProcessHost::Observer:
|
|
void OnServiceProcessLaunched(const ServiceProcessInfo& info) override {
|
|
if (info.IsService<echo::mojom::EchoService>()) {
|
|
process_ = info.GetProcess().Duplicate();
|
|
launch_loop_.Quit();
|
|
}
|
|
}
|
|
|
|
void OnServiceProcessTerminatedNormally(
|
|
const ServiceProcessInfo& info) override {
|
|
if (info.IsService<echo::mojom::EchoService>())
|
|
death_loop_.Quit();
|
|
}
|
|
|
|
void OnServiceProcessCrashed(const ServiceProcessInfo& info) override {
|
|
if (info.IsService<echo::mojom::EchoService>()) {
|
|
ASSERT_EQ(info.site(), GURL(kTestUrl));
|
|
crash_loop_.Quit();
|
|
}
|
|
}
|
|
|
|
base::RunLoop launch_loop_;
|
|
base::RunLoop death_loop_;
|
|
base::RunLoop crash_loop_;
|
|
base::Process process_;
|
|
};
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, Launch) {
|
|
EchoServiceProcessObserver observer;
|
|
base::ProcessId pid_from_callback = base::kNullProcessId;
|
|
base::RunLoop pid_loop;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>(
|
|
ServiceProcessHost::Options()
|
|
.WithProcessCallback(
|
|
base::BindLambdaForTesting([&](const base::Process& process) {
|
|
pid_from_callback = process.Pid();
|
|
pid_loop.Quit();
|
|
}))
|
|
.Pass());
|
|
observer.WaitForLaunch();
|
|
pid_loop.Run();
|
|
EXPECT_EQ(pid_from_callback, observer.pid());
|
|
EXPECT_NE(base::kNullProcessId, pid_from_callback);
|
|
|
|
const std::string kTestString =
|
|
"Aurora borealis! At this time of year? At this time of day? "
|
|
"In this part of the country? Localized entirely within your kitchen?";
|
|
base::RunLoop loop;
|
|
echo_service->EchoString(
|
|
kTestString,
|
|
base::BindLambdaForTesting([&](const std::string& echoed_input) {
|
|
EXPECT_EQ(kTestString, echoed_input);
|
|
loop.Quit();
|
|
}));
|
|
loop.Run();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, LocalDisconnectQuits) {
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>();
|
|
observer.WaitForLaunch();
|
|
echo_service.reset();
|
|
observer.WaitForDeath();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, RemoteDisconnectQuits) {
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>();
|
|
observer.WaitForLaunch();
|
|
echo_service->Quit();
|
|
observer.WaitForDeath();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, AllMessagesReceived) {
|
|
// Verifies that messages sent right before disconnection are always received
|
|
// and dispatched by the service before it self-terminates.
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>();
|
|
|
|
const size_t kBufferSize = 256;
|
|
const auto kMessages = std::to_array<std::string>({
|
|
"I thought we were having steamed clams.",
|
|
"D'oh, no! I said steamed hams. That's what I call hamburgers.",
|
|
"You call hamburgers, \"steamed hams?\"",
|
|
"Yes. It's a regional dialect.",
|
|
});
|
|
auto region = base::UnsafeSharedMemoryRegion::Create(kBufferSize);
|
|
base::WritableSharedMemoryMapping mapping = region.Map();
|
|
memset(mapping.memory(), 0, kBufferSize);
|
|
|
|
// Send several messages, since it helps to verify a lack of raciness between
|
|
// service-side message dispatch and service termination.
|
|
for (const auto& message : kMessages) {
|
|
ASSERT_LE(message.size(), kBufferSize);
|
|
echo_service->EchoStringToSharedMemory(message, region.Duplicate());
|
|
}
|
|
echo_service.reset();
|
|
observer.WaitForDeath();
|
|
|
|
const std::string& kLastMessage = kMessages[std::size(kMessages) - 1];
|
|
EXPECT_EQ(0,
|
|
memcmp(mapping.memory(), kLastMessage.data(), kLastMessage.size()));
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, ObserveCrash) {
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>(
|
|
ServiceProcessHost::Options().WithSite(GURL(kTestUrl)).Pass());
|
|
observer.WaitForLaunch();
|
|
echo_service->Crash();
|
|
observer.WaitForCrash();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, IdleTimeout) {
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>();
|
|
|
|
base::RunLoop wait_for_idle_loop;
|
|
constexpr auto kTimeout = base::Seconds(1);
|
|
echo_service.set_idle_handler(kTimeout, base::BindLambdaForTesting([&] {
|
|
wait_for_idle_loop.Quit();
|
|
echo_service.reset();
|
|
}));
|
|
|
|
// Send a message and wait for the reply. Once the message is sent we should
|
|
// observe at least |kTimeout| time elapsing before the RunLoop quits, because
|
|
// the service process must wait at least that long to report itself as idle.
|
|
base::ElapsedTimer timer;
|
|
const std::string kTestString =
|
|
"Yes, and you call them steamed hams despite the fact that they are "
|
|
"obviously grilled.";
|
|
echo_service->EchoString(
|
|
kTestString,
|
|
base::BindLambdaForTesting([&](const std::string& echoed_input) {
|
|
EXPECT_EQ(kTestString, echoed_input);
|
|
}));
|
|
wait_for_idle_loop.Run();
|
|
EXPECT_GE(timer.Elapsed(), kTimeout);
|
|
|
|
// And since the idle handler resets |echo_service|, we should imminently see
|
|
// normal service process termination.
|
|
observer.WaitForDeath();
|
|
}
|
|
|
|
#if BUILDFLAG(IS_WIN)
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, PreloadLibraryNotSet) {
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>(
|
|
ServiceProcessHost::Options().Pass());
|
|
observer.WaitForLaunch();
|
|
|
|
base::RunLoop loop;
|
|
echo_service->LoadNativeLibrary(
|
|
GetDllPath(kEchoPreloadLibrary), /*call_sec32_delayload=*/false,
|
|
base::BindLambdaForTesting([&](LoadStatus status, uint32_t result) {
|
|
EXPECT_EQ(LoadStatus::kFailedLoadLibrary, status);
|
|
EXPECT_EQ(DWORD{ERROR_ACCESS_DENIED}, result);
|
|
loop.Quit();
|
|
}));
|
|
loop.Run();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, PreloadLibraryPreloaded) {
|
|
std::vector<base::FilePath> preloads;
|
|
preloads.push_back(GetDllPath(kEchoPreloadLibrary));
|
|
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>(
|
|
ServiceProcessHost::Options()
|
|
.WithPreloadedLibraries(
|
|
preloads, ServiceProcessHostPreloadLibraries::GetPassKey())
|
|
.Pass());
|
|
observer.WaitForLaunch();
|
|
|
|
base::RunLoop loop;
|
|
echo_service->LoadNativeLibrary(
|
|
GetDllPath(kEchoPreloadLibrary),
|
|
/*call_sec32_delayload=*/true,
|
|
base::BindLambdaForTesting([&](LoadStatus status, uint32_t result) {
|
|
EXPECT_EQ(LoadStatus::kSuccess, status);
|
|
EXPECT_EQ(0u, result);
|
|
loop.Quit();
|
|
}));
|
|
loop.Run();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, PreloadLibraryMultiple) {
|
|
std::vector<base::FilePath> preloads;
|
|
// dbghelp is a placeholder - it will likely be loaded already - this test is
|
|
// validating that multiple libraries can be sent into the child.
|
|
preloads.push_back(GetDllPath(L"dbghelp"));
|
|
preloads.push_back(GetDllPath(kEchoPreloadLibrary));
|
|
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>(
|
|
ServiceProcessHost::Options()
|
|
.WithPreloadedLibraries(
|
|
preloads, ServiceProcessHostPreloadLibraries::GetPassKey())
|
|
.Pass());
|
|
observer.WaitForLaunch();
|
|
|
|
base::RunLoop loop;
|
|
echo_service->LoadNativeLibrary(
|
|
GetDllPath(kEchoPreloadLibrary), /*call_sec32_delayload=*/false,
|
|
base::BindLambdaForTesting([&](LoadStatus status, uint32_t result) {
|
|
EXPECT_EQ(LoadStatus::kSuccess, status);
|
|
EXPECT_EQ(0u, result);
|
|
loop.Quit();
|
|
}));
|
|
loop.Run();
|
|
}
|
|
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, PreloadLibraryModName) {
|
|
std::vector<base::FilePath> preloads;
|
|
preloads.push_back(GetDllPath(kEchoPreloadLibrary));
|
|
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>(
|
|
ServiceProcessHost::Options()
|
|
.WithPreloadedLibraries(
|
|
preloads, ServiceProcessHostPreloadLibraries::GetPassKey())
|
|
.Pass());
|
|
observer.WaitForLaunch();
|
|
|
|
base::RunLoop loop;
|
|
// Once preloaded can people simply provide the module name?
|
|
echo_service->LoadNativeLibrary(
|
|
base::FilePath(kEchoPreloadLibrary), /*call_sec32_delayload=*/false,
|
|
base::BindLambdaForTesting([&](LoadStatus status, uint32_t result) {
|
|
EXPECT_EQ(LoadStatus::kSuccess, status);
|
|
EXPECT_EQ(0u, result);
|
|
loop.Quit();
|
|
}));
|
|
loop.Run();
|
|
}
|
|
|
|
// This test causes a CHECK in the child at startup.
|
|
IN_PROC_BROWSER_TEST_F(ServiceProcessHostBrowserTest, PreloadLibraryBadPath) {
|
|
std::vector<base::FilePath> preloads;
|
|
preloads.push_back(GetDllPath(L"this-is-not-a-library"));
|
|
|
|
EchoServiceProcessObserver observer;
|
|
auto echo_service = ServiceProcessHost::Launch<echo::mojom::EchoService>(
|
|
ServiceProcessHost::Options()
|
|
.WithSite(GURL(kTestUrl)) // For WaitForCrash().
|
|
.WithPreloadedLibraries(
|
|
preloads, ServiceProcessHostPreloadLibraries::GetPassKey())
|
|
.Pass());
|
|
observer.WaitForLaunch();
|
|
observer.WaitForCrash();
|
|
}
|
|
#endif // BUILDFLAG(IS_WIN)
|
|
|
|
} // namespace content
|