chromium/sandbox/policy/win/sandbox_win_unittest.cc

// Copyright 2018 The Chromium Authors
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "sandbox/policy/win/sandbox_win.h"

#include <windows.h>

#include <memory>
#include <set>
#include <string>
#include <vector>

#include "base/command_line.h"
#include "base/files/file_path.h"
#include "base/files/file_util.h"
#include "base/files/scoped_temp_dir.h"
#include "base/logging.h"
#include "base/notreached.h"
#include "base/path_service.h"
#include "base/scoped_native_library.h"
#include "base/test/scoped_amount_of_physical_memory_override.h"
#include "base/test/scoped_feature_list.h"
#include "base/types/expected.h"
#include "base/win/security_descriptor.h"
#include "base/win/sid.h"
#include "base/win/windows_version.h"
#include "build/build_config.h"
#include "sandbox/policy/features.h"
#include "sandbox/policy/mojom/sandbox.mojom.h"
#include "sandbox/policy/sandbox_type.h"
#include "sandbox/policy/switches.h"
#include "sandbox/policy/win/lpac_capability.h"
#include "sandbox/win/src/app_container_base.h"
#include "sandbox/win/src/broker_services.h"
#include "sandbox/win/src/sandbox_factory.h"
#include "sandbox/win/src/sandbox_policy.h"
#include "sandbox/win/src/sandbox_policy_diagnostic.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"

using ::testing::_;
using ::testing::Return;

namespace sandbox {
namespace policy {

namespace {

class TestTargetConfig : public TargetConfig {
 public:
  ~TestTargetConfig() override {}
  bool IsConfigured() const override { return false; }
  ResultCode SetTokenLevel(sandbox::TokenLevel initial,
                           TokenLevel lockdown) override {
    return SBOX_ALL_OK;
  }
  TokenLevel GetInitialTokenLevel() const override { return TokenLevel{}; }
  TokenLevel GetLockdownTokenLevel() const override { return TokenLevel{}; }
  ResultCode SetJobLevel(sandbox::JobLevel job_level,
                         uint32_t ui_exceptions) override {
    return SBOX_ALL_OK;
  }
  JobLevel GetJobLevel() const override { return sandbox::JobLevel{}; }
  void SetJobMemoryLimit(size_t memory_limit) override {}
  ResultCode AllowFileAccess(FileSemantics semantics,
                             const wchar_t* pattern) override {
    return SBOX_ALL_OK;
  }
  ResultCode AllowExtraDlls(const wchar_t* pattern) override {
    return SBOX_ALL_OK;
  }
  ResultCode SetFakeGdiInit() override { return SBOX_ALL_OK; }
  void AddDllToUnload(const wchar_t* dll_name) override {
    blocklisted_dlls_.push_back(dll_name);
  }
  const std::vector<std::wstring>& blocklisted_dlls() const {
    return blocklisted_dlls_;
  }
  ResultCode SetIntegrityLevel(IntegrityLevel level) override {
    return SBOX_ALL_OK;
  }
  IntegrityLevel GetIntegrityLevel() const override { return IntegrityLevel{}; }
  void SetDelayedIntegrityLevel(IntegrityLevel level) override {}
  ResultCode SetLowBox(const wchar_t* sid) override { return SBOX_ALL_OK; }
  ResultCode SetProcessMitigations(MitigationFlags flags) override {
    return SBOX_ALL_OK;
  }
  MitigationFlags GetProcessMitigations() override { return MitigationFlags{}; }
  ResultCode SetDelayedProcessMitigations(MitigationFlags flags) override {
    return SBOX_ALL_OK;
  }
  MitigationFlags GetDelayedProcessMitigations() const override {
    return MitigationFlags{};
  }
  void AddRestrictingRandomSid() override {}
  void SetLockdownDefaultDacl() override {}
  void AddKernelObjectToClose(HandleToClose handle_info) override {}
  void SetDisconnectCsrss() override {}

  ResultCode AddAppContainerProfile(
      const wchar_t* package_name,
      ACProfileRegistration registration) override {
    app_container_ = AppContainerBase::Open(package_name);
    if (!app_container_) {
      return SBOX_ERROR_CREATE_APPCONTAINER;
    }
    return SBOX_ALL_OK;
  }

  AppContainer* GetAppContainer() override { return app_container_.get(); }

  std::unique_ptr<AppContainerBase> TakeAppContainerBase() {
    return std::move(app_container_);
  }

  void SetDesktop(Desktop desktop) override {}
  void SetFilterEnvironment(bool env) override {}
  bool GetEnvironmentFiltered() override { return false; }
  void SetZeroAppShim() override {}

 private:
  std::vector<std::wstring> blocklisted_dlls_;
  std::unique_ptr<AppContainerBase> app_container_;
};

// Drops a temporary file granting RX access to a list of capabilities.
bool DropTempFileWithSecurity(
    const base::ScopedTempDir& temp_dir,
    const std::initializer_list<std::wstring>& capabilities,
    base::FilePath* path) {
  if (!base::CreateTemporaryFileInDir(temp_dir.GetPath(), path)) {
    return false;
  }

  base::win::SecurityDescriptor sd;
  CHECK(sd.SetDaclEntry(base::win::WellKnownSid::kWorld,
                        base::win::SecurityAccessMode::kGrant, GENERIC_ALL, 0));
  for (const std::wstring& capability : capabilities) {
    CHECK(sd.SetDaclEntry(base::win::Sid::FromNamedCapability(capability),
                          base::win::SecurityAccessMode::kGrant,
                          GENERIC_READ | GENERIC_EXECUTE, 0));
  }
  sd.set_dacl_protected(true);
  return sd.WriteToFile(*path, DACL_SECURITY_INFORMATION);
}

void AddSidsToSet(std::set<std::wstring>& sid_set,
                  const std::vector<base::win::Sid>& sids) {
  for (const base::win::Sid& sid : sids) {
    sid_set.insert(*sid.ToSddlString());
  }
}

void AddSidsToSet(std::set<std::wstring>& sid_set,
                  const std::vector<std::wstring>& sids) {
  AddSidsToSet(sid_set, base::win::Sid::FromNamedCapabilityVector(sids));
}

void CompareSidList(const std::set<std::wstring>& sid_set,
                    const std::vector<base::win::Sid>& compare_sids) {
  std::set<std::wstring> compare_set;
  AddSidsToSet(compare_set, compare_sids);
  EXPECT_EQ(sid_set, compare_set);
}

base::FilePath GetShortPathName(const base::FilePath& path) {
  WCHAR short_path[MAX_PATH];
  DWORD size = ::GetShortPathName(path.value().c_str(), short_path,
                                  std::size(short_path));
  if (size == 0 || size >= MAX_PATH) {
    return {};
  }
  return base::FilePath(short_path);
}

struct AppContainerProfileTest {
  sandbox::mojom::Sandbox sandbox_type;
  std::wstring package_sid;
  bool lpac_enabled;
  std::vector<std::wstring> capabilities;
  std::vector<std::wstring> impersonation_capabilities;

  void Check(
      base::expected<std::unique_ptr<AppContainerBase>, ResultCode> result,
      const std::vector<std::wstring>& additional_capabilities) const {
    ASSERT_TRUE(result.has_value());
    auto profile = std::move(result.value());
    ASSERT_NE(nullptr, profile);
    EXPECT_EQ(package_sid, profile->GetPackageSid().ToSddlString());
    EXPECT_EQ(profile->GetEnableLowPrivilegeAppContainer(), lpac_enabled);

    std::set<std::wstring> base_caps;
    AddSidsToSet(base_caps, capabilities);
    AddSidsToSet(base_caps, additional_capabilities);
    std::set<std::wstring> impersonation_caps(base_caps.begin(),
                                              base_caps.end());
    AddSidsToSet(impersonation_caps, impersonation_capabilities);

    CompareSidList(base_caps, profile->GetCapabilities());
    CompareSidList(impersonation_caps, profile->GetImpersonationCapabilities());
  }
};

class SandboxWinTest : public ::testing::Test {
 public:
  SandboxWinTest() {}

  void SetUp() override { ASSERT_TRUE(temp_dir_.CreateUniqueTempDir()); }

  void TearDown() override {}

 protected:
  void CreateProgramFile(std::initializer_list<std::wstring> capabilities,
                         base::CommandLine* command_line) {
    base::FilePath path;
    ASSERT_TRUE(DropTempFileWithSecurity(temp_dir_, capabilities, &path));
    command_line->SetProgram(path);
  }

  base::expected<std::unique_ptr<AppContainerBase>, ResultCode>
  CreateAppContainerProfile(const base::CommandLine& base_command_line,
                            bool access_check_fail,
                            sandbox::mojom::Sandbox sandbox_type) {
    base::FilePath path;
    base::CommandLine command_line(base_command_line);

    if (access_check_fail) {
      CreateProgramFile({}, &command_line);
    } else {
      CreateProgramFile({kChromeInstallFiles, kLpacChromeInstallFiles},
                        &command_line);
    }

    std::string appcontainer_id = testing::UnitTest::GetInstance()
                                      ->current_test_info()
                                      ->test_suite_name();
    appcontainer_id += ".";
    appcontainer_id +=
        testing::UnitTest::GetInstance()->current_test_info()->name();
    TestTargetConfig config;
    ResultCode result = SandboxWin::AddAppContainerProfileToConfig(
        command_line, sandbox_type, appcontainer_id, &config);
    if (result != SBOX_ALL_OK) {
      return base::unexpected(result);
    }
    return config.TakeAppContainerBase();
  }

  base::ScopedTempDir temp_dir_;
};

}  // namespace

TEST_F(SandboxWinTest, IsGpuAppContainerEnabled) {
  // Unlike the other tests below that merely test App Container behavior, and
  // can rely on RS1 version check, the GPU App Container feature is gated on
  // RS5. See sandbox::features::IsAppContainerSandboxSupported.
  if (base::win::GetVersion() < base::win::Version::WIN10_RS5) {
    return;
  }
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  EXPECT_FALSE(SandboxWin::IsAppContainerEnabledForSandbox(
      command_line, sandbox::mojom::Sandbox::kGpu));
  base::test::ScopedFeatureList features;
  features.InitAndEnableFeature(features::kGpuAppContainer);
  EXPECT_TRUE(SandboxWin::IsAppContainerEnabledForSandbox(
      command_line, sandbox::mojom::Sandbox::kGpu));
  EXPECT_FALSE(SandboxWin::IsAppContainerEnabledForSandbox(
      command_line, sandbox::mojom::Sandbox::kNoSandbox));
}

TEST_F(SandboxWinTest, AppContainerAccessCheckFail) {
  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
    return;
  }
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  auto result = CreateAppContainerProfile(command_line, true,
                                          sandbox::mojom::Sandbox::kGpu);
  ASSERT_FALSE(result.has_value());
  EXPECT_EQ(SBOX_ERROR_CREATE_APPCONTAINER_ACCESS_CHECK, result.error());
}

TEST_F(SandboxWinTest, AppContainerCheckProfile) {
  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
    return;
  }
  constexpr wchar_t kInternetClient[] = L"internetClient";
  constexpr wchar_t kPrivateNetworkClientServer[] =
      L"privateNetworkClientServer";
  constexpr wchar_t kEnterpriseAuthentication[] = L"enterpriseAuthentication";

  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  const AppContainerProfileTest kProfileTests[] = {
      {sandbox::mojom::Sandbox::kGpu,
       L"S-1-15-2-2402834154-1919024995-1520873375-1190013510-771931769-"
       L"834570634-3212001585",
       true,
       {kLpacPnpNotifications, kLpacChromeInstallFiles, kRegistryRead},
       {kChromeInstallFiles}},
      {sandbox::mojom::Sandbox::kXrCompositing,
       L"S-1-15-2-1030503276-452227668-393455601-3654269295-1389305662-"
       L"158182952-2716868087",
       false,
       {kLpacPnpNotifications, kLpacChromeInstallFiles, kRegistryRead,
        kChromeInstallFiles},
       {}},
      {sandbox::mojom::Sandbox::kMediaFoundationCdm,
       L"S-1-15-2-3120300879-4058611061-160032764-3562819503-6834604-256341318-"
       L"1442147363",
       true,
       {kInternetClient, kPrivateNetworkClientServer, kLpacChromeInstallFiles,
        kRegistryRead, kLpacCom, kLpacIdentityServices, kLpacMedia,
        kLpacPnPNotifications, kLpacServicesManagement, kLpacSessionManagement,
        kLpacAppExperience, kLpacInstrumentation, kLpacCryptoServices,
        kLpacEnterprisePolicyChangeNotifications, kMediaFoundationCdmFiles,
        kMediaFoundationCdmData},
       {}},
      {sandbox::mojom::Sandbox::kNetwork,
       L"S-1-15-2-1204153576-2881085000-2101973085-273300490-2415804912-"
       L"3587146283-1585457728",
       base::FeatureList::IsEnabled(
           features::kWinSboxNetworkServiceSandboxIsLPAC),
       {kInternetClient, kPrivateNetworkClientServer, kEnterpriseAuthentication,
        kLpacIdentityServices, kLpacCryptoServices, kLpacChromeInstallFiles,
        kRegistryRead},
       {}},
      {sandbox::mojom::Sandbox::kWindowsSystemProxyResolver,
       L"S-1-15-2-1733900417-1595997880-1847635518-1308794714-877418578-"
       L"3685220290-3324296907",
       true,
       {kInternetClient, kLpacServicesManagement,
        kLpacEnterprisePolicyChangeNotifications, kLpacChromeInstallFiles,
        kRegistryRead},
       {}},
  };
  for (const AppContainerProfileTest& test : kProfileTests) {
    test.Check(
        CreateAppContainerProfile(command_line, false, test.sandbox_type), {});
  }
}

TEST_F(SandboxWinTest, AppContainerCheckProfileDisableLpac) {
  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
    return;
  }
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  base::test::ScopedFeatureList features;
  features.InitAndDisableFeature(features::kGpuLPAC);
  auto result = CreateAppContainerProfile(command_line, false,
                                          sandbox::mojom::Sandbox::kGpu);
  ASSERT_TRUE(result.has_value());
  ASSERT_NE(nullptr, result.value());
  EXPECT_FALSE(result.value()->GetEnableLowPrivilegeAppContainer());
}

TEST_F(SandboxWinTest, AppContainerCheckProfileAddCapabilities) {
  if (base::win::GetVersion() < base::win::Version::WIN10_RS1) {
    return;
  }
  base::CommandLine command_line(base::CommandLine::NO_PROGRAM);
  command_line.AppendSwitchASCII(switches::kAddGpuAppContainerCaps,
                                 "  cap1   ,   cap2   ,");
  const AppContainerProfileTest test{
      sandbox::mojom::Sandbox::kGpu,
      L"S-1-15-2-342359568-3976368142-3454201986-142512210-2527158890-"
      L"3531919343-1556627910",
      true,
      {kLpacPnpNotifications, kLpacChromeInstallFiles, kRegistryRead},
      {kChromeInstallFiles}};
  test.Check(CreateAppContainerProfile(command_line, false,
                                       sandbox::mojom::Sandbox::kGpu),
             {L"cap1", L"cap2"});
}

TEST_F(SandboxWinTest, BlocklistAddOneDllCheckInBrowser) {
  {  // Block loaded module.
    TestTargetConfig config;
    BlocklistAddOneDllForTesting(L"kernel32.dll", &config);
    EXPECT_EQ(config.blocklisted_dlls(),
              std::vector<std::wstring>({L"kernel32.dll"}));
  }

  {  // Block module which is not loaded.
    TestTargetConfig config;
    BlocklistAddOneDllForTesting(L"notloaded.dll", &config);
    EXPECT_TRUE(config.blocklisted_dlls().empty());
  }

  {
    base::FilePath executable_path;
    ASSERT_TRUE(base::PathService::Get(base::FILE_EXE, &executable_path));
    constexpr wchar_t kFullDllName[] = L"longfilename.dll";
    base::FilePath dll_path = temp_dir_.GetPath().Append(kFullDllName);

    ASSERT_TRUE(base::CopyFile(executable_path, dll_path));
    base::FilePath short_path = GetShortPathName(dll_path);
    base::FilePath short_name = short_path.BaseName();
    if (short_path.empty() ||
        base::EqualsCaseInsensitiveASCII(short_name.value(), kFullDllName)) {
      LOG(WARNING) << short_path.value()
                   << " doesn't have a short path. Ignoring remaining tests.";
      return;
    }

    base::ScopedNativeLibrary library(short_path);
    ASSERT_TRUE(library.is_valid());

    TestTargetConfig config;
    BlocklistAddOneDllForTesting(kFullDllName, &config);
    EXPECT_EQ(config.blocklisted_dlls(),
              std::vector<std::wstring>({short_name.value(), kFullDllName}));
  }
}

// Sandbox can't reach into content to pull the real policies, so these tests
// merely verifies that various parts of the delegate are called correctly and a
// policy can be generated.
class TestSandboxDelegate : public SandboxDelegate {
 public:
  TestSandboxDelegate(sandbox::mojom::Sandbox sandbox_type)
      : sandbox_type_(sandbox_type) {}
  sandbox::mojom::Sandbox GetSandboxType() override { return sandbox_type_; }
  bool DisableDefaultPolicy() override { return false; }
  bool GetAppContainerId(std::string* appcontainer_id) override {
    NOTREACHED_IN_MIGRATION();
    return false;
  }

  MOCK_METHOD1(InitializeConfig, bool(TargetConfig* config));
  MOCK_METHOD1(PreSpawnTarget, bool(TargetPolicy* policy));

  std::string GetSandboxTag() override { return std::string(); }
  void PostSpawnTarget(base::ProcessHandle process) override {}

  bool ShouldUnsandboxedRunInJob() override { return false; }

  bool CetCompatible() override { return true; }
  bool AllowWindowsFontsDir() override { return true; }

 private:
  sandbox::mojom::Sandbox sandbox_type_;
};

TEST_F(SandboxWinTest, GeneratedPolicyTest) {
  TestSandboxDelegate test_renderer_delegate(
      sandbox::mojom::Sandbox::kRenderer);
  base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
  base::HandlesToInheritVector handles_to_inherit;
  BrokerServices* broker = SandboxFactory::GetBrokerServices();
  auto policy = broker->CreatePolicy();
  EXPECT_CALL(test_renderer_delegate, InitializeConfig(_))
      .WillOnce(Return(true));
  // PreSpawn should get called, but not modifying the policy for this test.
  EXPECT_CALL(test_renderer_delegate, PreSpawnTarget(_)).WillOnce(Return(true));
  ResultCode result = SandboxWin::GeneratePolicyForSandboxedProcess(
      cmd_line, handles_to_inherit, &test_renderer_delegate, policy.get());
  ASSERT_EQ(ResultCode::SBOX_ALL_OK, result);
  // Check some default values come back. No need to check the exact policy in
  // detail, but just that GeneratePolicyForSandboxedProcess generated some kind
  // of valid policy.
  EXPECT_EQ(IntegrityLevel::INTEGRITY_LEVEL_LOW,
            policy->GetConfig()->GetIntegrityLevel());
  EXPECT_EQ(JobLevel::kLockdown, policy->GetConfig()->GetJobLevel());
  EXPECT_EQ(TokenLevel::USER_LOCKDOWN,
            policy->GetConfig()->GetLockdownTokenLevel());
}

TEST_F(SandboxWinTest, GeneratedPolicyTestMultipleCalls) {
  TestSandboxDelegate test_renderer_delegate(
      sandbox::mojom::Sandbox::kRenderer);
  base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
  base::HandlesToInheritVector handles_to_inherit;
  BrokerServices* broker = SandboxFactory::GetBrokerServices();
  auto policy = broker->CreatePolicy();

  // Checks that multiple initializations of the policy only initialize the
  // configuration once but calls PreSpawnTarget twice.
  EXPECT_CALL(test_renderer_delegate, InitializeConfig(_))
      .WillOnce(Return(true));
  EXPECT_CALL(test_renderer_delegate, PreSpawnTarget(_))
      .Times(2)
      .WillRepeatedly(Return(true));
  ResultCode result = SandboxWin::GeneratePolicyForSandboxedProcess(
      cmd_line, handles_to_inherit, &test_renderer_delegate, policy.get());
  ASSERT_EQ(ResultCode::SBOX_ALL_OK, result);
  BrokerServicesBase::FreezeTargetConfigForTesting(policy->GetConfig());
  result = SandboxWin::GeneratePolicyForSandboxedProcess(
      cmd_line, handles_to_inherit, &test_renderer_delegate, policy.get());
  ASSERT_EQ(ResultCode::SBOX_ALL_OK, result);
}

TEST_F(SandboxWinTest, GeneratedPolicyTestNoSandbox) {
  TestSandboxDelegate test_unsandboxed_delegate(
      sandbox::mojom::Sandbox::kNoSandbox);
  base::CommandLine cmd_line(base::CommandLine::NO_PROGRAM);
  base::HandlesToInheritVector handles_to_inherit;
  BrokerServices* broker = SandboxFactory::GetBrokerServices();
  auto policy = broker->CreatePolicy();
  // Unsandboxed processes never call the delegate prespawn as there is no
  // policy.
  EXPECT_CALL(test_unsandboxed_delegate, InitializeConfig(_)).Times(0);
  EXPECT_CALL(test_unsandboxed_delegate, PreSpawnTarget(_)).Times(0);

  ResultCode result = SandboxWin::GeneratePolicyForSandboxedProcess(
      cmd_line, handles_to_inherit, &test_unsandboxed_delegate, policy.get());
  ASSERT_EQ(ResultCode::SBOX_ERROR_UNSANDBOXED_PROCESS, result);
}

TEST_F(SandboxWinTest, GetJobMemoryLimit) {
  constexpr uint64_t k8GB = 8192;
#if defined(ARCH_CPU_64_BITS)
  constexpr uint64_t kGB = 1024 * 1024 * 1024;
  constexpr uint64_t k65GB = 66560;
  constexpr uint64_t k33GB = 33792;
  constexpr uint64_t k17GB = 17408;

  // Test GPU with physical memory > 64GB.
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k65GB);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
    EXPECT_TRUE(memory_limit.has_value());
    EXPECT_EQ(memory_limit, 64 * kGB);
  }

  // Test GPU with physical memory > 32GB
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k33GB);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
    EXPECT_TRUE(memory_limit.has_value());
    EXPECT_EQ(memory_limit, 32 * kGB);
  }

  // Test GPU with physical memory > 16GB
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k17GB);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
    EXPECT_TRUE(memory_limit.has_value());
    EXPECT_EQ(memory_limit, 16 * kGB);
  }

  // Test GPU with physical memory < 16GB
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kGpu);
    EXPECT_TRUE(memory_limit.has_value());
    EXPECT_EQ(memory_limit, 8 * kGB);
  }

  // Test Renderer with physical memory > 16GB
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k17GB);
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndDisableFeature(
        sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
    EXPECT_TRUE(memory_limit.has_value());
    EXPECT_EQ(memory_limit, 16 * kGB);
  }

  // Test Renderer with physical memory < 16GB
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndDisableFeature(
        sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
    EXPECT_TRUE(memory_limit.has_value());
    EXPECT_EQ(memory_limit, 8 * kGB);
  }

  // Test Renderer with high renderer limits enabled.
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
    base::test::ScopedFeatureList scoped_feature_list;
    scoped_feature_list.InitAndEnableFeature(
        sandbox::policy::features::kWinSboxHighRendererJobMemoryLimits);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
    EXPECT_TRUE(memory_limit.has_value());
    EXPECT_EQ(memory_limit, 1024 * kGB);
  }
#else
  // Test 32-bit processes don't get a limit.
  {
    base::test::ScopedAmountOfPhysicalMemoryOverride memory_override(k8GB);
    std::optional<size_t> memory_limit =
        SandboxWin::GetJobMemoryLimit(sandbox::mojom::Sandbox::kRenderer);
    EXPECT_FALSE(memory_limit.has_value());
  }
#endif  // defined(ARCH_CPU_64_BITS)
}

}  // namespace policy
}  // namespace sandbox