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