1// Copyright 2018 The Chromium Authors. All rights reserved. 2// Use of this source code is governed by a BSD-style license that can be 3// found in the LICENSE file. 4 5#import <Cocoa/Cocoa.h> 6#import <Foundation/Foundation.h> 7 8#include <fcntl.h> 9 10#include "base/bind.h" 11#include "base/callback.h" 12#include "base/command_line.h" 13#include "base/files/file_util.h" 14#include "base/files/scoped_file.h" 15#include "base/mac/foundation_util.h" 16#include "base/mac/mac_util.h" 17#include "base/mac/scoped_cftyperef.h" 18#include "base/memory/ref_counted.h" 19#include "base/posix/eintr_wrapper.h" 20#include "base/process/kill.h" 21#include "base/strings/strcat.h" 22#include "base/strings/stringprintf.h" 23#include "base/strings/sys_string_conversions.h" 24#include "base/strings/utf_string_conversions.h" 25#include "base/test/multiprocess_test.h" 26#include "base/test/test_timeouts.h" 27#include "content/browser/sandbox_parameters_mac.h" 28#include "content/common/mac/font_loader.h" 29#include "crypto/openssl_util.h" 30#include "sandbox/mac/seatbelt.h" 31#include "sandbox/mac/seatbelt_exec.h" 32#include "sandbox/policy/mac/sandbox_mac.h" 33#include "sandbox/policy/switches.h" 34#include "testing/gtest/include/gtest/gtest.h" 35#include "testing/multiprocess_func_list.h" 36#include "third_party/boringssl/src/include/openssl/rand.h" 37#import "ui/base/clipboard/clipboard_util_mac.h" 38 39namespace content { 40namespace { 41 42// crbug.com/740009: This allows the unit test to cleanup temporary directories, 43// and is safe since this is only a unit test. 44constexpr char kTempDirSuffix[] = 45 "(allow file* (subpath \"/private/var/folders\"))"; 46constexpr char kExtraDataArg[] = "extra-data"; 47 48class SandboxMacTest : public base::MultiProcessTest { 49 protected: 50 base::CommandLine MakeCmdLine(const std::string& procname) override { 51 base::CommandLine cl = MultiProcessTest::MakeCmdLine(procname); 52 cl.AppendArg( 53 base::StringPrintf("%s%d", sandbox::switches::kSeatbeltClient, pipe_)); 54 if (!extra_data_.empty()) { 55 cl.AppendSwitchASCII(kExtraDataArg, extra_data_); 56 } 57 return cl; 58 } 59 60 void ExecuteWithParams(const std::string& procname, 61 sandbox::policy::SandboxType sandbox_type) { 62 std::string profile = 63 sandbox::policy::SandboxMac::GetSandboxProfile(sandbox_type) + 64 kTempDirSuffix; 65 sandbox::SeatbeltExecClient client; 66 client.SetProfile(profile); 67 SetupSandboxParameters(sandbox_type, 68 *base::CommandLine::ForCurrentProcess(), &client); 69 70 pipe_ = client.GetReadFD(); 71 ASSERT_GE(pipe_, 0); 72 73 base::LaunchOptions options; 74 options.fds_to_remap.push_back(std::make_pair(pipe_, pipe_)); 75 76 base::Process process = SpawnChildWithOptions(procname, options); 77 ASSERT_TRUE(process.IsValid()); 78 ASSERT_TRUE(client.SendProfile()); 79 80 int rv = -1; 81 ASSERT_TRUE(base::WaitForMultiprocessTestChildExit( 82 process, TestTimeouts::action_timeout(), &rv)); 83 EXPECT_EQ(0, rv); 84 } 85 86 void ExecuteInAllSandboxTypes(const std::string& multiprocess_main, 87 base::RepeatingClosure after_each) { 88 constexpr sandbox::policy::SandboxType kSandboxTypes[] = { 89 sandbox::policy::SandboxType::kAudio, 90 sandbox::policy::SandboxType::kCdm, 91 sandbox::policy::SandboxType::kGpu, 92 sandbox::policy::SandboxType::kNaClLoader, 93 sandbox::policy::SandboxType::kPpapi, 94 sandbox::policy::SandboxType::kPrintCompositor, 95 sandbox::policy::SandboxType::kRenderer, 96 sandbox::policy::SandboxType::kUtility, 97 }; 98 99 for (const auto type : kSandboxTypes) { 100 ExecuteWithParams(multiprocess_main, type); 101 if (!after_each.is_null()) { 102 after_each.Run(); 103 } 104 } 105 } 106 107 int pipe_{0}; 108 std::string extra_data_{}; 109}; 110 111void CheckCreateSeatbeltServer() { 112 base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); 113 const base::CommandLine::StringVector& argv = cl->argv(); 114 std::vector<char*> argv_cstr(argv.size()); 115 for (size_t i = 0; i < argv.size(); ++i) { 116 argv_cstr[i] = const_cast<char*>(argv[i].c_str()); 117 } 118 auto result = sandbox::SeatbeltExecServer::CreateFromArguments( 119 argv_cstr[0], argv_cstr.size(), argv_cstr.data()); 120 121 CHECK(result.sandbox_required); 122 CHECK(result.server); 123 CHECK(result.server->InitializeSandbox()); 124} 125 126std::string GetExtraDataValue() { 127 base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); 128 return cl->GetSwitchValueASCII(kExtraDataArg); 129} 130 131} // namespace 132 133MULTIPROCESS_TEST_MAIN(RendererWriteProcess) { 134 CheckCreateSeatbeltServer(); 135 136 // Test that the renderer cannot write to the home directory. 137 NSString* test_file = [NSHomeDirectory() 138 stringByAppendingPathComponent:@"e539dd6f-6b38-4f6a-af2c-809a5ea96e1c"]; 139 int fd = HANDLE_EINTR( 140 open(base::SysNSStringToUTF8(test_file).c_str(), O_CREAT | O_RDWR)); 141 CHECK(-1 == fd); 142 CHECK_EQ(errno, EPERM); 143 144 return 0; 145} 146 147TEST_F(SandboxMacTest, RendererCannotWriteHomeDir) { 148 ExecuteWithParams("RendererWriteProcess", 149 sandbox::policy::SandboxType::kRenderer); 150} 151 152MULTIPROCESS_TEST_MAIN(ClipboardAccessProcess) { 153 CheckCreateSeatbeltServer(); 154 155 std::string pasteboard_name = GetExtraDataValue(); 156 CHECK(!pasteboard_name.empty()); 157 CHECK([NSPasteboard pasteboardWithName:base::SysUTF8ToNSString( 158 pasteboard_name)] == nil); 159 CHECK([NSPasteboard generalPasteboard] == nil); 160 161 return 0; 162} 163 164TEST_F(SandboxMacTest, ClipboardAccess) { 165 scoped_refptr<ui::UniquePasteboard> pb = new ui::UniquePasteboard; 166 ASSERT_TRUE(pb->get()); 167 EXPECT_EQ([[pb->get() types] count], 0U); 168 169 extra_data_ = base::SysNSStringToUTF8([pb->get() name]); 170 171 ExecuteInAllSandboxTypes("ClipboardAccessProcess", 172 base::BindRepeating( 173 [](scoped_refptr<ui::UniquePasteboard> pb) { 174 ASSERT_EQ([[pb->get() types] count], 0U); 175 }, 176 pb)); 177} 178 179MULTIPROCESS_TEST_MAIN(SSLProcess) { 180 CheckCreateSeatbeltServer(); 181 182 crypto::EnsureOpenSSLInit(); 183 // Ensure that RAND_bytes is functional within the sandbox. 184 uint8_t byte; 185 CHECK(RAND_bytes(&byte, 1) == 1); 186 return 0; 187} 188 189TEST_F(SandboxMacTest, SSLInitTest) { 190 ExecuteInAllSandboxTypes("SSLProcess", base::RepeatingClosure()); 191} 192 193MULTIPROCESS_TEST_MAIN(FontLoadingProcess) { 194 // Create a shared memory handle to mimic what the browser process does. 195 std::string font_file_path = GetExtraDataValue(); 196 CHECK(!font_file_path.empty()); 197 198 std::string font_data; 199 CHECK(base::ReadFileToString(base::FilePath(font_file_path), &font_data)); 200 201 size_t font_data_length = font_data.length(); 202 CHECK(font_data_length > 0); 203 204 auto font_shmem = mojo::SharedBufferHandle::Create(font_data_length); 205 CHECK(font_shmem.is_valid()); 206 207 mojo::ScopedSharedBufferMapping mapping = font_shmem->Map(font_data_length); 208 CHECK(mapping); 209 210 memcpy(mapping.get(), font_data.c_str(), font_data_length); 211 212 // Now init the sandbox. 213 CheckCreateSeatbeltServer(); 214 215 mojo::ScopedSharedBufferHandle shmem_handle = 216 font_shmem->Clone(mojo::SharedBufferHandle::AccessMode::READ_ONLY); 217 CHECK(shmem_handle.is_valid()); 218 219 base::ScopedCFTypeRef<CTFontDescriptorRef> data_descriptor; 220 CHECK(FontLoader::CTFontDescriptorFromBuffer( 221 std::move(shmem_handle), font_data_length, &data_descriptor)); 222 CHECK(data_descriptor); 223 224 base::ScopedCFTypeRef<CTFontRef> sized_ctfont( 225 CTFontCreateWithFontDescriptor(data_descriptor.get(), 16.0, nullptr)); 226 CHECK(sized_ctfont); 227 228 // Do something with the font to make sure it's loaded. 229 CGFloat cap_height = CTFontGetCapHeight(sized_ctfont); 230 CHECK(cap_height > 0.0); 231 232 return 0; 233} 234 235TEST_F(SandboxMacTest, FontLoadingTest) { 236 base::FilePath temp_file_path; 237 base::ScopedFILE temp_file = 238 base::CreateAndOpenTemporaryStream(&temp_file_path); 239 ASSERT_TRUE(temp_file); 240 241 std::unique_ptr<FontLoader::ResultInternal> result = 242 FontLoader::LoadFontForTesting(base::ASCIIToUTF16("Geeza Pro"), 16); 243 ASSERT_TRUE(result); 244 ASSERT_TRUE(result->font_data.is_valid()); 245 uint64_t font_data_size = result->font_data->GetSize(); 246 EXPECT_GT(font_data_size, 0U); 247 EXPECT_GT(result->font_id, 0U); 248 249 mojo::ScopedSharedBufferMapping mapping = 250 result->font_data->Map(font_data_size); 251 ASSERT_TRUE(mapping); 252 253 base::WriteFileDescriptor(fileno(temp_file.get()), 254 static_cast<const char*>(mapping.get()), 255 font_data_size); 256 257 extra_data_ = temp_file_path.value(); 258 ExecuteWithParams("FontLoadingProcess", 259 sandbox::policy::SandboxType::kRenderer); 260 temp_file.reset(); 261 ASSERT_TRUE(base::DeleteFile(temp_file_path)); 262} 263 264MULTIPROCESS_TEST_MAIN(BuiltinAvailable) { 265 CheckCreateSeatbeltServer(); 266 267 if (__builtin_available(macOS 10.10, *)) { 268 // Can't negate a __builtin_available condition. But success! 269 } else { 270 return 10; 271 } 272 273 if (base::mac::IsAtLeastOS10_13()) { 274 if (__builtin_available(macOS 10.13, *)) { 275 // Can't negate a __builtin_available condition. But success! 276 } else { 277 return 13; 278 } 279 } 280 281 return 0; 282} 283 284TEST_F(SandboxMacTest, BuiltinAvailable) { 285 ExecuteInAllSandboxTypes("BuiltinAvailable", {}); 286} 287 288MULTIPROCESS_TEST_MAIN(NetworkProcessPrefs) { 289 CheckCreateSeatbeltServer(); 290 291 const std::string kBundleId = base::mac::BaseBundleID(); 292 const std::string kUserName = base::SysNSStringToUTF8(NSUserName()); 293 const std::vector<std::string> kPaths = { 294 "/Library/Managed Preferences/.GlobalPreferences.plist", 295 base::StrCat({"/Library/Managed Preferences/", kBundleId, ".plist"}), 296 base::StrCat({"/Library/Managed Preferences/", kUserName, 297 "/.GlobalPreferences.plist"}), 298 base::StrCat({"/Library/Managed Preferences/", kUserName, "/", kBundleId, 299 ".plist"}), 300 base::StrCat({"/Library/Preferences/", kBundleId, ".plist"}), 301 base::StrCat({"/Users/", kUserName, 302 "/Library/Preferences/com.apple.security.plist"}), 303 base::StrCat( 304 {"/Users/", kUserName, "/Library/Preferences/", kBundleId, ".plist"}), 305 }; 306 307 for (const auto& path : kPaths) { 308 // Use open rather than stat to test file-read-data rules. 309 base::ScopedFD fd(open(path.c_str(), O_RDONLY)); 310 PCHECK(fd.is_valid() || errno == ENOENT) << path; 311 } 312 313 return 0; 314} 315 316TEST_F(SandboxMacTest, NetworkProcessPrefs) { 317 ExecuteWithParams("NetworkProcessPrefs", 318 sandbox::policy::SandboxType::kNetwork); 319} 320 321} // namespace content 322