blob: fb4f3d0149fbd3813adb5d7e2b4f484d4215271a [file] [log] [blame]
// Copyright 2024 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include "base/strings/string_number_conversions.h"
#include "base/test/bind.h"
#include "base/test/scoped_feature_list.h"
#include "base/values.h"
#include "build/build_config.h"
#include "content/browser/web_contents/web_contents_impl.h"
#include "content/public/test/browser_test.h"
#include "content/public/test/browser_test_base.h"
#include "content/public/test/content_browser_test.h"
#include "content/public/test/content_browser_test_content_browser_client.h"
#include "content/public/test/content_browser_test_utils.h"
#include "content/public/test/test_utils.h"
#include "content/shell/browser/shell.h"
#include "net/dns/mock_host_resolver.h"
#include "sandbox/policy/features.h"
#include "testing/gtest/include/gtest/gtest.h"
#if BUILDFLAG(IS_WIN)
#include "sandbox/policy/win/sandbox_win.h"
#endif
namespace content {
namespace {
constexpr char kEnabledDomain[] = "enable-jit.net";
constexpr char kDisabledDomain[] = "disable-jit.net";
bool RendererIsJitless(RenderProcessHost* rph) {
// It would be nice if we could also check the renderer process's actual
// command line here, but there's no portable interface to do so.
return rph->IsJitDisabled();
}
#if BUILDFLAG(IS_WIN)
bool RendererHasDynamicCodeMitigation(RenderProcessHost* rph) {
// Multiple renderer processes might have started. Grab a reference to the
// base::Process itself as well as grabbing the process ID; this ensures that
// the process doesn't actually die during the RunLoop::Run() call below, so
// its pid cannot be reused and confuse the test.
base::Process proc = rph->GetProcess().Duplicate();
base::ProcessId renderer_process_id = proc.Pid();
base::RunLoop run_loop;
base::Value out_args;
sandbox::policy::SandboxWin::GetPolicyDiagnostics(
base::BindLambdaForTesting([&run_loop, &out_args](base::Value args) {
out_args = std::move(args);
run_loop.Quit();
}));
run_loop.Run();
const base::Value::List* process_list = out_args.GetIfList();
CHECK(process_list);
for (const base::Value& process_value : *process_list) {
const base::Value::Dict* process = process_value.GetIfDict();
CHECK(process);
double pid = *process->FindDouble("processId");
if (base::checked_cast<base::ProcessId>(pid) != renderer_process_id) {
continue;
}
std::string mitigations = *process->FindString("desiredMitigations");
uint64_t mask = 0;
CHECK(base::HexStringToUInt64(mitigations, &mask));
return !!(mask & sandbox::MITIGATION_DYNAMIC_CODE_DISABLE);
}
return false;
}
#endif // IS_WIN
} // namespace
class JitPolicyContentBrowserClient
: public ContentBrowserTestContentBrowserClient {
public:
bool IsJitDisabledForSite(BrowserContext* context, const GURL& url) override {
// This is a bit confusing: for kEnabledDomain, JIT is enabled, so
// IsJitDisabled() returns false. For kDisabledDomain, JIT is disabled, so
// IsJitDisabled() returns true. Otherwise, use the default policy.
if (url.DomainIs(kEnabledDomain)) {
return false;
} else if (url.DomainIs(kDisabledDomain)) {
return true;
} else {
return ContentBrowserTestContentBrowserClient::IsJitDisabledForSite(
context, url);
}
}
// Always enable renderer code integrity - without this, ProhibitDynamicCode
// never gets applied to the renderer anyway.
#if BUILDFLAG(IS_WIN)
bool IsRendererCodeIntegrityEnabled() override { return true; }
#endif // IS_WIN
};
// This test fixture installs a test ContentBrowserClient which enables JIT for
// kEnabledDomain and disables JIT for kDisabledDomain.
class JitPolicyBrowserTest : public ContentBrowserTest {
public:
JitPolicyBrowserTest() = default;
void SetUpCommandLine(base::CommandLine* command_line) override {
content::IsolateAllSitesForTesting(command_line);
}
void SetUpOnMainThread() override {
// Support multiple sites on the test server.
host_resolver()->AddRule("*", "127.0.0.1");
content_browser_client_ = std::make_unique<JitPolicyContentBrowserClient>();
}
protected:
WebContentsImpl* contents() const {
return static_cast<WebContentsImpl*>(shell()->web_contents());
}
private:
// Constructing this object causes it to register itself as a
// ContentBrowserClient.
std::unique_ptr<JitPolicyContentBrowserClient> content_browser_client_;
};
// This test asserts that navigating to a renderer which has JIT disabled yields
// a jitless renderer process with the DynamicCode mitigation applied.
IN_PROC_BROWSER_TEST_F(JitPolicyBrowserTest, JitDisabledImpliesJitless) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(shell(), embedded_test_server()->GetURL(
kDisabledDomain, "/title1.html")));
RenderProcessHost* rph = contents()->GetPrimaryMainFrame()->GetProcess();
// With JIT disabled, the renderer process should be jitless and have the
// DynamicCode mitigation applied.
EXPECT_TRUE(RendererIsJitless(rph));
#if BUILDFLAG(IS_WIN)
EXPECT_TRUE(RendererHasDynamicCodeMitigation(rph));
#endif
}
// This test asserts that navigating to a JIT-enabled site results in a renderer
// process that is not jitless and does not have the dynamic code mitigation
// enabled.
IN_PROC_BROWSER_TEST_F(JitPolicyBrowserTest, JitEnabledImpliesNoJitless) {
ASSERT_TRUE(embedded_test_server()->Start());
EXPECT_TRUE(NavigateToURL(
shell(), embedded_test_server()->GetURL(kEnabledDomain, "/title1.html")));
RenderProcessHost* rph = contents()->GetPrimaryMainFrame()->GetProcess();
// With JIT enabled, the renderer process should have JIT and not have the
// DynamicCode mitigation applied.
EXPECT_FALSE(RendererIsJitless(rph));
#if BUILDFLAG(IS_WIN)
EXPECT_FALSE(RendererHasDynamicCodeMitigation(rph));
#endif
}
} // namespace content