blob: 27ccf2e37b9176bb6f3e38dbca322c8d94930464 [file] [log] [blame]
Avi Drissman4e1b7bc32022-09-15 14:03:501// Copyright 2018 The Chromium Authors
Greg Kerr65f314fb2018-11-09 20:35:252// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
Evan Stade526e35a62025-02-01 00:09:375#include "sandbox/policy/mac/sandbox_mac.h"
6
Greg Kerrb25043d2018-11-27 18:25:467#import <Cocoa/Cocoa.h>
Greg Kerr65f314fb2018-11-09 20:35:258#import <Foundation/Foundation.h>
Robert Sesek6826fb62020-11-12 22:23:179#include <fcntl.h>
10
Avi Drissmaneac566b02023-08-18 02:56:2111#include "base/apple/foundation_util.h"
Avi Drissmana09d7dd2023-08-17 16:26:5812#include "base/apple/scoped_cftyperef.h"
Greg Kerr65f314fb2018-11-09 20:35:2513#include "base/command_line.h"
Greg Kerr95d0a572018-11-30 21:41:1214#include "base/files/file_util.h"
15#include "base/files/scoped_file.h"
Avi Drissmanadac21992023-01-11 23:46:3916#include "base/functional/bind.h"
17#include "base/functional/callback.h"
Robert Sesek8c3ca5e2020-08-07 19:22:5718#include "base/mac/mac_util.h"
Daniel Cheng93c88962022-03-18 00:54:4819#include "base/memory/read_only_shared_memory_region.h"
Greg Kerrb25043d2018-11-27 18:25:4620#include "base/memory/ref_counted.h"
Daniel Cheng93c88962022-03-18 00:54:4821#include "base/memory/shared_memory_mapping.h"
Greg Kerr65f314fb2018-11-09 20:35:2522#include "base/posix/eintr_wrapper.h"
23#include "base/process/kill.h"
Robert Sesek6826fb62020-11-12 22:23:1724#include "base/strings/strcat.h"
Greg Kerr65f314fb2018-11-09 20:35:2525#include "base/strings/stringprintf.h"
26#include "base/strings/sys_string_conversions.h"
Greg Kerr95d0a572018-11-30 21:41:1227#include "base/strings/utf_string_conversions.h"
Greg Kerr65f314fb2018-11-09 20:35:2528#include "base/test/multiprocess_test.h"
29#include "base/test/test_timeouts.h"
30#include "content/browser/sandbox_parameters_mac.h"
Evan Stade526e35a62025-02-01 00:09:3731#include "sandbox/mac/sandbox_serializer.h"
Greg Kerr65f314fb2018-11-09 20:35:2532#include "sandbox/mac/seatbelt.h"
33#include "sandbox/mac/seatbelt_exec.h"
Alex Gougheb6a38f2021-10-22 01:55:1334#include "sandbox/policy/mojom/sandbox.mojom.h"
Robert Sesek7d0b49b2020-07-08 18:31:2735#include "sandbox/policy/switches.h"
Greg Kerr65f314fb2018-11-09 20:35:2536#include "testing/gtest/include/gtest/gtest.h"
37#include "testing/multiprocess_func_list.h"
Greg Kerr5ffaaea2018-11-28 00:13:2938#include "third_party/boringssl/src/include/openssl/rand.h"
Greg Kerrb25043d2018-11-27 18:25:4639#import "ui/base/clipboard/clipboard_util_mac.h"
Greg Kerr65f314fb2018-11-09 20:35:2540
41namespace content {
42namespace {
43
44// crbug.com/740009: This allows the unit test to cleanup temporary directories,
45// and is safe since this is only a unit test.
46constexpr char kTempDirSuffix[] =
Giovanni Ortuño Urquidi1d2514d82018-11-30 01:58:1147 "(allow file* (subpath \"/private/var/folders\"))";
Greg Kerr95d0a572018-11-30 21:41:1248constexpr char kExtraDataArg[] = "extra-data";
Greg Kerr65f314fb2018-11-09 20:35:2549
50class SandboxMacTest : public base::MultiProcessTest {
51 protected:
52 base::CommandLine MakeCmdLine(const std::string& procname) override {
53 base::CommandLine cl = MultiProcessTest::MakeCmdLine(procname);
54 cl.AppendArg(
55 base::StringPrintf("%s%d", sandbox::switches::kSeatbeltClient, pipe_));
Greg Kerr95d0a572018-11-30 21:41:1256 if (!extra_data_.empty()) {
57 cl.AppendSwitchASCII(kExtraDataArg, extra_data_);
58 }
Greg Kerr65f314fb2018-11-09 20:35:2559 return cl;
60 }
61
Greg Kerrb25043d2018-11-27 18:25:4662 void ExecuteWithParams(const std::string& procname,
Alex Gougheb6a38f2021-10-22 01:55:1363 sandbox::mojom::Sandbox sandbox_type) {
Greg Kerrb7fdb792019-04-24 23:05:4664 std::string profile =
Robert Sesek5aef3522021-04-14 22:48:2365 sandbox::policy::GetSandboxProfile(sandbox_type) + kTempDirSuffix;
Evan Stade526e35a62025-02-01 00:09:3766 sandbox::SandboxSerializer serializer(
67 sandbox::SandboxSerializer::Target::kSource);
68
69 serializer.SetProfile(profile);
70 SetupSandboxParameters(
71 sandbox_type, *base::CommandLine::ForCurrentProcess(), &serializer);
72 std::string error, serialized;
73 CHECK(serializer.SerializePolicy(serialized, error)) << error;
Greg Kerrb25043d2018-11-27 18:25:4674
Robert Sesek794b0822022-12-05 15:22:2775 sandbox::SeatbeltExecClient client;
Greg Kerrb25043d2018-11-27 18:25:4676 pipe_ = client.GetReadFD();
77 ASSERT_GE(pipe_, 0);
78
79 base::LaunchOptions options;
Avi Drissman7ed05532023-06-06 20:34:2980 options.fds_to_remap.emplace_back(pipe_, pipe_);
Greg Kerrb25043d2018-11-27 18:25:4681
82 base::Process process = SpawnChildWithOptions(procname, options);
83 ASSERT_TRUE(process.IsValid());
Evan Stade526e35a62025-02-01 00:09:3784 ASSERT_TRUE(client.SendPolicy(serialized));
Greg Kerrb25043d2018-11-27 18:25:4685
86 int rv = -1;
87 ASSERT_TRUE(base::WaitForMultiprocessTestChildExit(
88 process, TestTimeouts::action_timeout(), &rv));
89 EXPECT_EQ(0, rv);
90 }
91
Greg Kerrb25043d2018-11-27 18:25:4692 void ExecuteInAllSandboxTypes(const std::string& multiprocess_main,
93 base::RepeatingClosure after_each) {
Alex Gougheb6a38f2021-10-22 01:55:1394 constexpr sandbox::mojom::Sandbox kSandboxTypes[] = {
95 sandbox::mojom::Sandbox::kAudio,
96 sandbox::mojom::Sandbox::kCdm,
97 sandbox::mojom::Sandbox::kGpu,
Alex Gougheb6a38f2021-10-22 01:55:1398 sandbox::mojom::Sandbox::kPrintBackend,
99 sandbox::mojom::Sandbox::kPrintCompositor,
100 sandbox::mojom::Sandbox::kRenderer,
101 sandbox::mojom::Sandbox::kService,
Alex Gough72421352021-12-21 11:08:31102 sandbox::mojom::Sandbox::kServiceWithJit,
Alex Gougheb6a38f2021-10-22 01:55:13103 sandbox::mojom::Sandbox::kUtility,
Greg Kerrb25043d2018-11-27 18:25:46104 };
105
Greg Kerrb7fdb792019-04-24 23:05:46106 for (const auto type : kSandboxTypes) {
107 ExecuteWithParams(multiprocess_main, type);
Greg Kerr5ffaaea2018-11-28 00:13:29108 if (!after_each.is_null()) {
109 after_each.Run();
110 }
Greg Kerrb25043d2018-11-27 18:25:46111 }
112 }
113
Greg Kerr65f314fb2018-11-09 20:35:25114 int pipe_{0};
Greg Kerr95d0a572018-11-30 21:41:12115 std::string extra_data_{};
Greg Kerrb25043d2018-11-27 18:25:46116};
117
Greg Kerr65f314fb2018-11-09 20:35:25118void CheckCreateSeatbeltServer() {
119 base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
120 const base::CommandLine::StringVector& argv = cl->argv();
121 std::vector<char*> argv_cstr(argv.size());
122 for (size_t i = 0; i < argv.size(); ++i) {
123 argv_cstr[i] = const_cast<char*>(argv[i].c_str());
124 }
125 auto result = sandbox::SeatbeltExecServer::CreateFromArguments(
126 argv_cstr[0], argv_cstr.size(), argv_cstr.data());
127
128 CHECK(result.sandbox_required);
129 CHECK(result.server);
130 CHECK(result.server->InitializeSandbox());
131}
132
Greg Kerr95d0a572018-11-30 21:41:12133std::string GetExtraDataValue() {
134 base::CommandLine* cl = base::CommandLine::ForCurrentProcess();
135 return cl->GetSwitchValueASCII(kExtraDataArg);
136}
137
Greg Kerr65f314fb2018-11-09 20:35:25138} // namespace
139
140MULTIPROCESS_TEST_MAIN(RendererWriteProcess) {
141 CheckCreateSeatbeltServer();
142
143 // Test that the renderer cannot write to the home directory.
144 NSString* test_file = [NSHomeDirectory()
145 stringByAppendingPathComponent:@"e539dd6f-6b38-4f6a-af2c-809a5ea96e1c"];
146 int fd = HANDLE_EINTR(
147 open(base::SysNSStringToUTF8(test_file).c_str(), O_CREAT | O_RDWR));
148 CHECK(-1 == fd);
149 CHECK_EQ(errno, EPERM);
150
151 return 0;
152}
153
154TEST_F(SandboxMacTest, RendererCannotWriteHomeDir) {
Alex Gougheb6a38f2021-10-22 01:55:13155 ExecuteWithParams("RendererWriteProcess", sandbox::mojom::Sandbox::kRenderer);
Greg Kerrb25043d2018-11-27 18:25:46156}
Greg Kerr65f314fb2018-11-09 20:35:25157
Greg Kerrb25043d2018-11-27 18:25:46158MULTIPROCESS_TEST_MAIN(ClipboardAccessProcess) {
159 CheckCreateSeatbeltServer();
Greg Kerr65f314fb2018-11-09 20:35:25160
Greg Kerr95d0a572018-11-30 21:41:12161 std::string pasteboard_name = GetExtraDataValue();
Greg Kerrb25043d2018-11-27 18:25:46162 CHECK(!pasteboard_name.empty());
163 CHECK([NSPasteboard pasteboardWithName:base::SysUTF8ToNSString(
164 pasteboard_name)] == nil);
Avi Drissman7ed05532023-06-06 20:34:29165 CHECK(NSPasteboard.generalPasteboard == nil);
Greg Kerr65f314fb2018-11-09 20:35:25166
Greg Kerrb25043d2018-11-27 18:25:46167 return 0;
168}
Greg Kerr65f314fb2018-11-09 20:35:25169
Greg Kerr95d0a572018-11-30 21:41:12170TEST_F(SandboxMacTest, ClipboardAccess) {
Greg Kerrb25043d2018-11-27 18:25:46171 scoped_refptr<ui::UniquePasteboard> pb = new ui::UniquePasteboard;
172 ASSERT_TRUE(pb->get());
Avi Drissman7ed05532023-06-06 20:34:29173 EXPECT_EQ(pb->get().types.count, 0U);
Greg Kerr65f314fb2018-11-09 20:35:25174
Avi Drissman7ed05532023-06-06 20:34:29175 extra_data_ = base::SysNSStringToUTF8(pb->get().name);
Greg Kerrb25043d2018-11-27 18:25:46176
177 ExecuteInAllSandboxTypes("ClipboardAccessProcess",
178 base::BindRepeating(
179 [](scoped_refptr<ui::UniquePasteboard> pb) {
180 ASSERT_EQ([[pb->get() types] count], 0U);
181 },
182 pb));
Greg Kerr65f314fb2018-11-09 20:35:25183}
184
Greg Kerr5ffaaea2018-11-28 00:13:29185MULTIPROCESS_TEST_MAIN(SSLProcess) {
186 CheckCreateSeatbeltServer();
187
Greg Kerr5ffaaea2018-11-28 00:13:29188 // Ensure that RAND_bytes is functional within the sandbox.
189 uint8_t byte;
190 CHECK(RAND_bytes(&byte, 1) == 1);
191 return 0;
192}
193
194TEST_F(SandboxMacTest, SSLInitTest) {
195 ExecuteInAllSandboxTypes("SSLProcess", base::RepeatingClosure());
196}
197
Avi Drissman7ed05532023-06-06 20:34:29198// This test checks to make sure that `__builtin_available()` (and therefore the
199// Objective-C equivalent `@available()`) work within a sandbox. When revving
200// the macOS releases supported by Chromium, bump this up. This value
201// specifically matches the oldest macOS release supported by Chromium.
Robert Sesek955648b2020-08-07 17:18:24202MULTIPROCESS_TEST_MAIN(BuiltinAvailable) {
203 CheckCreateSeatbeltServer();
204
Avi Drissman9aea9cc72025-05-29 14:41:00205 if (__builtin_available(macOS 12, *)) {
Robert Sesek955648b2020-08-07 17:18:24206 // Can't negate a __builtin_available condition. But success!
207 } else {
Avi Drissman9390d282023-06-26 23:14:49208 return 15;
Robert Sesek955648b2020-08-07 17:18:24209 }
210
211 return 0;
212}
213
214TEST_F(SandboxMacTest, BuiltinAvailable) {
215 ExecuteInAllSandboxTypes("BuiltinAvailable", {});
216}
217
Robert Sesek6826fb62020-11-12 22:23:17218MULTIPROCESS_TEST_MAIN(NetworkProcessPrefs) {
219 CheckCreateSeatbeltServer();
220
Avi Drissman370a58102025-05-06 15:54:54221 const std::string kBundleId(base::apple::BaseBundleID());
Robert Sesek6826fb62020-11-12 22:23:17222 const std::string kUserName = base::SysNSStringToUTF8(NSUserName());
223 const std::vector<std::string> kPaths = {
224 "/Library/Managed Preferences/.GlobalPreferences.plist",
225 base::StrCat({"/Library/Managed Preferences/", kBundleId, ".plist"}),
226 base::StrCat({"/Library/Managed Preferences/", kUserName,
227 "/.GlobalPreferences.plist"}),
228 base::StrCat({"/Library/Managed Preferences/", kUserName, "/", kBundleId,
229 ".plist"}),
230 base::StrCat({"/Library/Preferences/", kBundleId, ".plist"}),
231 base::StrCat({"/Users/", kUserName,
232 "/Library/Preferences/com.apple.security.plist"}),
233 base::StrCat(
234 {"/Users/", kUserName, "/Library/Preferences/", kBundleId, ".plist"}),
235 };
236
237 for (const auto& path : kPaths) {
238 // Use open rather than stat to test file-read-data rules.
239 base::ScopedFD fd(open(path.c_str(), O_RDONLY));
240 PCHECK(fd.is_valid() || errno == ENOENT) << path;
241 }
242
243 return 0;
244}
245
246TEST_F(SandboxMacTest, NetworkProcessPrefs) {
Alex Gougheb6a38f2021-10-22 01:55:13247 ExecuteWithParams("NetworkProcessPrefs", sandbox::mojom::Sandbox::kNetwork);
Robert Sesek6826fb62020-11-12 22:23:17248}
249
Greg Kerr65f314fb2018-11-09 20:35:25250} // namespace content