blob: 819b70cc8d5294ebd2b6431d7986b33fd8d723b9 [file] [log] [blame]
// Copyright 2018 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#import <Cocoa/Cocoa.h>
#import <Foundation/Foundation.h>
#include "base/bind.h"
#include "base/callback.h"
#include "base/command_line.h"
#include "base/files/file_util.h"
#include "base/files/scoped_file.h"
#include "base/mac/scoped_cftyperef.h"
#include "base/memory/ref_counted.h"
#include "base/memory/shared_memory.h"
#include "base/posix/eintr_wrapper.h"
#include "base/process/kill.h"
#include "base/strings/stringprintf.h"
#include "base/strings/sys_string_conversions.h"
#include "base/strings/utf_string_conversions.h"
#include "base/test/multiprocess_test.h"
#include "base/test/test_timeouts.h"
#include "content/browser/sandbox_parameters_mac.h"
#include "content/common/mac/font_loader.h"
#include "crypto/openssl_util.h"
#include "sandbox/mac/seatbelt.h"
#include "sandbox/mac/seatbelt_exec.h"
#include "services/service_manager/sandbox/mac/audio.sb.h"
#include "services/service_manager/sandbox/mac/cdm.sb.h"
#include "services/service_manager/sandbox/mac/common.sb.h"
#include "services/service_manager/sandbox/mac/gpu_v2.sb.h"
#include "services/service_manager/sandbox/mac/nacl_loader.sb.h"
#include "services/service_manager/sandbox/mac/pdf_compositor.sb.h"
#include "services/service_manager/sandbox/mac/ppapi.sb.h"
#include "services/service_manager/sandbox/mac/renderer.sb.h"
#include "services/service_manager/sandbox/mac/utility.sb.h"
#include "services/service_manager/sandbox/switches.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "testing/multiprocess_func_list.h"
#include "third_party/boringssl/src/include/openssl/rand.h"
#import "ui/base/clipboard/clipboard_util_mac.h"
namespace content {
namespace {
// crbug.com/740009: This allows the unit test to cleanup temporary directories,
// and is safe since this is only a unit test.
constexpr char kTempDirSuffix[] =
"(allow file* (subpath \"/private/var/folders\"))";
constexpr char kExtraDataArg[] = "extra-data";
class SandboxMacTest : public base::MultiProcessTest {
protected:
base::CommandLine MakeCmdLine(const std::string& procname) override {
base::CommandLine cl = MultiProcessTest::MakeCmdLine(procname);
cl.AppendArg(
base::StringPrintf("%s%d", sandbox::switches::kSeatbeltClient, pipe_));
if (!extra_data_.empty()) {
cl.AppendSwitchASCII(kExtraDataArg, extra_data_);
}
return cl;
}
std::string ProfileForSandbox(const std::string& sandbox_profile) {
return std::string(service_manager::kSeatbeltPolicyString_common) +
sandbox_profile + kTempDirSuffix;
}
void ExecuteWithParams(const std::string& procname,
const std::string& sub_profile,
void (*setup)(sandbox::SeatbeltExecClient*)) {
std::string profile = ProfileForSandbox(sub_profile);
sandbox::SeatbeltExecClient client;
client.SetProfile(profile);
setup(&client);
pipe_ = client.GetReadFD();
ASSERT_GE(pipe_, 0);
base::LaunchOptions options;
options.fds_to_remap.push_back(std::make_pair(pipe_, pipe_));
base::Process process = SpawnChildWithOptions(procname, options);
ASSERT_TRUE(process.IsValid());
ASSERT_TRUE(client.SendProfile());
int rv = -1;
ASSERT_TRUE(base::WaitForMultiprocessTestChildExit(
process, TestTimeouts::action_timeout(), &rv));
EXPECT_EQ(0, rv);
}
void ExecuteInAudioSandbox(const std::string& procname) {
ExecuteWithParams(procname, service_manager::kSeatbeltPolicyString_audio,
&content::SetupCommonSandboxParameters);
}
void ExecuteInCDMSandbox(const std::string& procname) {
ExecuteWithParams(procname, service_manager::kSeatbeltPolicyString_cdm,
&content::SetupCDMSandboxParameters);
}
void ExecuteInGPUSandbox(const std::string& procname) {
ExecuteWithParams(procname, service_manager::kSeatbeltPolicyString_gpu_v2,
&content::SetupCommonSandboxParameters);
}
void ExecuteInNaClSandbox(const std::string& procname) {
ExecuteWithParams(procname,
service_manager::kSeatbeltPolicyString_nacl_loader,
&content::SetupCommonSandboxParameters);
}
void ExecuteInPDFSandbox(const std::string& procname) {
ExecuteWithParams(procname,
service_manager::kSeatbeltPolicyString_pdf_compositor,
&content::SetupCommonSandboxParameters);
}
void ExecuteInPpapiSandbox(const std::string& procname) {
ExecuteWithParams(procname, service_manager::kSeatbeltPolicyString_ppapi,
&content::SetupPPAPISandboxParameters);
}
void ExecuteInRendererSandbox(const std::string& procname) {
ExecuteWithParams(procname, service_manager::kSeatbeltPolicyString_renderer,
&content::SetupCommonSandboxParameters);
}
void ExecuteInUtilitySandbox(const std::string& procname) {
ExecuteWithParams(procname, service_manager::kSeatbeltPolicyString_utility,
[](sandbox::SeatbeltExecClient* client) -> void {
content::SetupUtilitySandboxParameters(
client, *base::CommandLine::ForCurrentProcess());
});
}
void ExecuteInAllSandboxTypes(const std::string& multiprocess_main,
base::RepeatingClosure after_each) {
using ExecuteFuncT = void (SandboxMacTest::*)(const std::string&);
constexpr ExecuteFuncT kExecuteFuncs[] = {
&SandboxMacTest::ExecuteInAudioSandbox,
&SandboxMacTest::ExecuteInCDMSandbox,
&SandboxMacTest::ExecuteInGPUSandbox,
&SandboxMacTest::ExecuteInNaClSandbox,
&SandboxMacTest::ExecuteInPDFSandbox,
&SandboxMacTest::ExecuteInPpapiSandbox,
&SandboxMacTest::ExecuteInRendererSandbox,
&SandboxMacTest::ExecuteInUtilitySandbox,
};
for (ExecuteFuncT execute_func : kExecuteFuncs) {
(this->*execute_func)(multiprocess_main);
if (!after_each.is_null()) {
after_each.Run();
}
}
}
int pipe_{0};
std::string extra_data_{};
};
void CheckCreateSeatbeltServer() {
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
const base::CommandLine::StringVector& argv = cl->argv();
std::vector<char*> argv_cstr(argv.size());
for (size_t i = 0; i < argv.size(); ++i) {
argv_cstr[i] = const_cast<char*>(argv[i].c_str());
}
auto result = sandbox::SeatbeltExecServer::CreateFromArguments(
argv_cstr[0], argv_cstr.size(), argv_cstr.data());
CHECK(result.sandbox_required);
CHECK(result.server);
CHECK(result.server->InitializeSandbox());
}
std::string GetExtraDataValue() {
base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
return cl->GetSwitchValueASCII(kExtraDataArg);
}
} // namespace
MULTIPROCESS_TEST_MAIN(RendererWriteProcess) {
CheckCreateSeatbeltServer();
// Test that the renderer cannot write to the home directory.
NSString* test_file = [NSHomeDirectory()
stringByAppendingPathComponent:@"e539dd6f-6b38-4f6a-af2c-809a5ea96e1c"];
int fd = HANDLE_EINTR(
open(base::SysNSStringToUTF8(test_file).c_str(), O_CREAT | O_RDWR));
CHECK(-1 == fd);
CHECK_EQ(errno, EPERM);
return 0;
}
TEST_F(SandboxMacTest, RendererCannotWriteHomeDir) {
ExecuteInRendererSandbox("RendererWriteProcess");
}
MULTIPROCESS_TEST_MAIN(ClipboardAccessProcess) {
CheckCreateSeatbeltServer();
std::string pasteboard_name = GetExtraDataValue();
CHECK(!pasteboard_name.empty());
CHECK([NSPasteboard pasteboardWithName:base::SysUTF8ToNSString(
pasteboard_name)] == nil);
CHECK([NSPasteboard generalPasteboard] == nil);
return 0;
}
TEST_F(SandboxMacTest, ClipboardAccess) {
scoped_refptr<ui::UniquePasteboard> pb = new ui::UniquePasteboard;
ASSERT_TRUE(pb->get());
EXPECT_EQ([[pb->get() types] count], 0U);
extra_data_ = base::SysNSStringToUTF8([pb->get() name]);
ExecuteInAllSandboxTypes("ClipboardAccessProcess",
base::BindRepeating(
[](scoped_refptr<ui::UniquePasteboard> pb) {
ASSERT_EQ([[pb->get() types] count], 0U);
},
pb));
}
MULTIPROCESS_TEST_MAIN(SSLProcess) {
CheckCreateSeatbeltServer();
crypto::EnsureOpenSSLInit();
// Ensure that RAND_bytes is functional within the sandbox.
uint8_t byte;
CHECK(RAND_bytes(&byte, 1) == 1);
return 0;
}
TEST_F(SandboxMacTest, SSLInitTest) {
ExecuteInAllSandboxTypes("SSLProcess", base::RepeatingClosure());
}
MULTIPROCESS_TEST_MAIN(FontLoadingProcess) {
// Create a shared memory handle to mimic what the browser process does.
std::string font_file_path = GetExtraDataValue();
CHECK(!font_file_path.empty());
std::string font_data;
CHECK(base::ReadFileToString(base::FilePath(font_file_path), &font_data));
size_t font_data_length = font_data.length();
CHECK(font_data_length > 0);
auto font_shmem = mojo::SharedBufferHandle::Create(font_data_length);
CHECK(font_shmem.is_valid());
mojo::ScopedSharedBufferMapping mapping = font_shmem->Map(font_data_length);
CHECK(mapping);
memcpy(mapping.get(), font_data.c_str(), font_data_length);
// Now init the sandbox.
CheckCreateSeatbeltServer();
mojo::ScopedSharedBufferHandle shmem_handle =
font_shmem->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY);
CHECK(shmem_handle.is_valid());
base::ScopedCFTypeRef<CGFontRef> cgfont;
CHECK(FontLoader::CGFontRefFromBuffer(
std::move(shmem_handle), font_data_length, cgfont.InitializeInto()));
CHECK(cgfont);
base::ScopedCFTypeRef<CTFontRef> ctfont(
CTFontCreateWithGraphicsFont(cgfont.get(), 16.0, NULL, NULL));
CHECK(ctfont);
// Do something with the font to make sure it's loaded.
CGFloat cap_height = CTFontGetCapHeight(ctfont);
CHECK(cap_height > 0.0);
return 0;
}
TEST_F(SandboxMacTest, FontLoadingTest) {
base::FilePath temp_file_path;
FILE* temp_file = base::CreateAndOpenTemporaryFile(&temp_file_path);
ASSERT_TRUE(temp_file);
base::ScopedFILE temp_file_closer(temp_file);
std::unique_ptr<FontLoader::ResultInternal> result =
FontLoader::LoadFontForTesting(base::ASCIIToUTF16("Geeza Pro"), 16);
ASSERT_TRUE(result);
ASSERT_TRUE(result->font_data.is_valid());
uint64_t font_data_size = result->font_data->GetSize();
EXPECT_GT(font_data_size, 0U);
EXPECT_GT(result->font_id, 0U);
mojo::ScopedSharedBufferMapping mapping =
result->font_data->Map(font_data_size);
ASSERT_TRUE(mapping);
base::WriteFileDescriptor(fileno(temp_file),
static_cast<const char*>(mapping.get()),
font_data_size);
extra_data_ = temp_file_path.value();
ExecuteInRendererSandbox("FontLoadingProcess");
temp_file_closer.reset();
ASSERT_TRUE(base::DeleteFile(temp_file_path, false));
}
} // namespace content