Avi Drissman | 4e1b7bc3 | 2022-09-15 14:03:50 | [diff] [blame] | 1 | // Copyright 2018 The Chromium Authors |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 2 | // Use of this source code is governed by a BSD-style license that can be |
| 3 | // found in the LICENSE file. |
| 4 | |
Evan Stade | 526e35a6 | 2025-02-01 00:09:37 | [diff] [blame] | 5 | #include "sandbox/policy/mac/sandbox_mac.h" |
| 6 | |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 7 | #import <Cocoa/Cocoa.h> |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 8 | #import <Foundation/Foundation.h> |
Robert Sesek | 6826fb6 | 2020-11-12 22:23:17 | [diff] [blame] | 9 | #include <fcntl.h> |
| 10 | |
Avi Drissman | eac566b0 | 2023-08-18 02:56:21 | [diff] [blame] | 11 | #include "base/apple/foundation_util.h" |
Avi Drissman | a09d7dd | 2023-08-17 16:26:58 | [diff] [blame] | 12 | #include "base/apple/scoped_cftyperef.h" |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 13 | #include "base/command_line.h" |
Greg Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 14 | #include "base/files/file_util.h" |
| 15 | #include "base/files/scoped_file.h" |
Avi Drissman | adac2199 | 2023-01-11 23:46:39 | [diff] [blame] | 16 | #include "base/functional/bind.h" |
| 17 | #include "base/functional/callback.h" |
Robert Sesek | 8c3ca5e | 2020-08-07 19:22:57 | [diff] [blame] | 18 | #include "base/mac/mac_util.h" |
Daniel Cheng | 93c8896 | 2022-03-18 00:54:48 | [diff] [blame] | 19 | #include "base/memory/read_only_shared_memory_region.h" |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 20 | #include "base/memory/ref_counted.h" |
Daniel Cheng | 93c8896 | 2022-03-18 00:54:48 | [diff] [blame] | 21 | #include "base/memory/shared_memory_mapping.h" |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 22 | #include "base/posix/eintr_wrapper.h" |
| 23 | #include "base/process/kill.h" |
Robert Sesek | 6826fb6 | 2020-11-12 22:23:17 | [diff] [blame] | 24 | #include "base/strings/strcat.h" |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 25 | #include "base/strings/stringprintf.h" |
| 26 | #include "base/strings/sys_string_conversions.h" |
Greg Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 27 | #include "base/strings/utf_string_conversions.h" |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 28 | #include "base/test/multiprocess_test.h" |
| 29 | #include "base/test/test_timeouts.h" |
| 30 | #include "content/browser/sandbox_parameters_mac.h" |
Evan Stade | 526e35a6 | 2025-02-01 00:09:37 | [diff] [blame] | 31 | #include "sandbox/mac/sandbox_serializer.h" |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 32 | #include "sandbox/mac/seatbelt.h" |
| 33 | #include "sandbox/mac/seatbelt_exec.h" |
Alex Gough | eb6a38f | 2021-10-22 01:55:13 | [diff] [blame] | 34 | #include "sandbox/policy/mojom/sandbox.mojom.h" |
Robert Sesek | 7d0b49b | 2020-07-08 18:31:27 | [diff] [blame] | 35 | #include "sandbox/policy/switches.h" |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 36 | #include "testing/gtest/include/gtest/gtest.h" |
| 37 | #include "testing/multiprocess_func_list.h" |
Greg Kerr | 5ffaaea | 2018-11-28 00:13:29 | [diff] [blame] | 38 | #include "third_party/boringssl/src/include/openssl/rand.h" |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 39 | #import "ui/base/clipboard/clipboard_util_mac.h" |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 40 | |
| 41 | namespace content { |
| 42 | namespace { |
| 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. |
| 46 | constexpr char kTempDirSuffix[] = |
Giovanni Ortuño Urquidi | 1d2514d8 | 2018-11-30 01:58:11 | [diff] [blame] | 47 | "(allow file* (subpath \"/private/var/folders\"))"; |
Greg Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 48 | constexpr char kExtraDataArg[] = "extra-data"; |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 49 | |
| 50 | class 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 Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 56 | if (!extra_data_.empty()) { |
| 57 | cl.AppendSwitchASCII(kExtraDataArg, extra_data_); |
| 58 | } |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 59 | return cl; |
| 60 | } |
| 61 | |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 62 | void ExecuteWithParams(const std::string& procname, |
Alex Gough | eb6a38f | 2021-10-22 01:55:13 | [diff] [blame] | 63 | sandbox::mojom::Sandbox sandbox_type) { |
Greg Kerr | b7fdb79 | 2019-04-24 23:05:46 | [diff] [blame] | 64 | std::string profile = |
Robert Sesek | 5aef352 | 2021-04-14 22:48:23 | [diff] [blame] | 65 | sandbox::policy::GetSandboxProfile(sandbox_type) + kTempDirSuffix; |
Evan Stade | 526e35a6 | 2025-02-01 00:09:37 | [diff] [blame] | 66 | 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 Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 74 | |
Robert Sesek | 794b082 | 2022-12-05 15:22:27 | [diff] [blame] | 75 | sandbox::SeatbeltExecClient client; |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 76 | pipe_ = client.GetReadFD(); |
| 77 | ASSERT_GE(pipe_, 0); |
| 78 | |
| 79 | base::LaunchOptions options; |
Avi Drissman | 7ed0553 | 2023-06-06 20:34:29 | [diff] [blame] | 80 | options.fds_to_remap.emplace_back(pipe_, pipe_); |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 81 | |
| 82 | base::Process process = SpawnChildWithOptions(procname, options); |
| 83 | ASSERT_TRUE(process.IsValid()); |
Evan Stade | 526e35a6 | 2025-02-01 00:09:37 | [diff] [blame] | 84 | ASSERT_TRUE(client.SendPolicy(serialized)); |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 85 | |
| 86 | int rv = -1; |
| 87 | ASSERT_TRUE(base::WaitForMultiprocessTestChildExit( |
| 88 | process, TestTimeouts::action_timeout(), &rv)); |
| 89 | EXPECT_EQ(0, rv); |
| 90 | } |
| 91 | |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 92 | void ExecuteInAllSandboxTypes(const std::string& multiprocess_main, |
| 93 | base::RepeatingClosure after_each) { |
Alex Gough | eb6a38f | 2021-10-22 01:55:13 | [diff] [blame] | 94 | constexpr sandbox::mojom::Sandbox kSandboxTypes[] = { |
| 95 | sandbox::mojom::Sandbox::kAudio, |
| 96 | sandbox::mojom::Sandbox::kCdm, |
| 97 | sandbox::mojom::Sandbox::kGpu, |
Alex Gough | eb6a38f | 2021-10-22 01:55:13 | [diff] [blame] | 98 | sandbox::mojom::Sandbox::kPrintBackend, |
| 99 | sandbox::mojom::Sandbox::kPrintCompositor, |
| 100 | sandbox::mojom::Sandbox::kRenderer, |
| 101 | sandbox::mojom::Sandbox::kService, |
Alex Gough | 7242135 | 2021-12-21 11:08:31 | [diff] [blame] | 102 | sandbox::mojom::Sandbox::kServiceWithJit, |
Alex Gough | eb6a38f | 2021-10-22 01:55:13 | [diff] [blame] | 103 | sandbox::mojom::Sandbox::kUtility, |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 104 | }; |
| 105 | |
Greg Kerr | b7fdb79 | 2019-04-24 23:05:46 | [diff] [blame] | 106 | for (const auto type : kSandboxTypes) { |
| 107 | ExecuteWithParams(multiprocess_main, type); |
Greg Kerr | 5ffaaea | 2018-11-28 00:13:29 | [diff] [blame] | 108 | if (!after_each.is_null()) { |
| 109 | after_each.Run(); |
| 110 | } |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 111 | } |
| 112 | } |
| 113 | |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 114 | int pipe_{0}; |
Greg Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 115 | std::string extra_data_{}; |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 116 | }; |
| 117 | |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 118 | void 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 Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 133 | std::string GetExtraDataValue() { |
| 134 | base::CommandLine* cl = base::CommandLine::ForCurrentProcess(); |
| 135 | return cl->GetSwitchValueASCII(kExtraDataArg); |
| 136 | } |
| 137 | |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 138 | } // namespace |
| 139 | |
| 140 | MULTIPROCESS_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 | |
| 154 | TEST_F(SandboxMacTest, RendererCannotWriteHomeDir) { |
Alex Gough | eb6a38f | 2021-10-22 01:55:13 | [diff] [blame] | 155 | ExecuteWithParams("RendererWriteProcess", sandbox::mojom::Sandbox::kRenderer); |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 156 | } |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 157 | |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 158 | MULTIPROCESS_TEST_MAIN(ClipboardAccessProcess) { |
| 159 | CheckCreateSeatbeltServer(); |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 160 | |
Greg Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 161 | std::string pasteboard_name = GetExtraDataValue(); |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 162 | CHECK(!pasteboard_name.empty()); |
| 163 | CHECK([NSPasteboard pasteboardWithName:base::SysUTF8ToNSString( |
| 164 | pasteboard_name)] == nil); |
Avi Drissman | 7ed0553 | 2023-06-06 20:34:29 | [diff] [blame] | 165 | CHECK(NSPasteboard.generalPasteboard == nil); |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 166 | |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 167 | return 0; |
| 168 | } |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 169 | |
Greg Kerr | 95d0a57 | 2018-11-30 21:41:12 | [diff] [blame] | 170 | TEST_F(SandboxMacTest, ClipboardAccess) { |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 171 | scoped_refptr<ui::UniquePasteboard> pb = new ui::UniquePasteboard; |
| 172 | ASSERT_TRUE(pb->get()); |
Avi Drissman | 7ed0553 | 2023-06-06 20:34:29 | [diff] [blame] | 173 | EXPECT_EQ(pb->get().types.count, 0U); |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 174 | |
Avi Drissman | 7ed0553 | 2023-06-06 20:34:29 | [diff] [blame] | 175 | extra_data_ = base::SysNSStringToUTF8(pb->get().name); |
Greg Kerr | b25043d | 2018-11-27 18:25:46 | [diff] [blame] | 176 | |
| 177 | ExecuteInAllSandboxTypes("ClipboardAccessProcess", |
| 178 | base::BindRepeating( |
| 179 | [](scoped_refptr<ui::UniquePasteboard> pb) { |
| 180 | ASSERT_EQ([[pb->get() types] count], 0U); |
| 181 | }, |
| 182 | pb)); |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 183 | } |
| 184 | |
Greg Kerr | 5ffaaea | 2018-11-28 00:13:29 | [diff] [blame] | 185 | MULTIPROCESS_TEST_MAIN(SSLProcess) { |
| 186 | CheckCreateSeatbeltServer(); |
| 187 | |
Greg Kerr | 5ffaaea | 2018-11-28 00:13:29 | [diff] [blame] | 188 | // 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 | |
| 194 | TEST_F(SandboxMacTest, SSLInitTest) { |
| 195 | ExecuteInAllSandboxTypes("SSLProcess", base::RepeatingClosure()); |
| 196 | } |
| 197 | |
Avi Drissman | 7ed0553 | 2023-06-06 20:34:29 | [diff] [blame] | 198 | // 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 Sesek | 955648b | 2020-08-07 17:18:24 | [diff] [blame] | 202 | MULTIPROCESS_TEST_MAIN(BuiltinAvailable) { |
| 203 | CheckCreateSeatbeltServer(); |
| 204 | |
Avi Drissman | 9aea9cc7 | 2025-05-29 14:41:00 | [diff] [blame] | 205 | if (__builtin_available(macOS 12, *)) { |
Robert Sesek | 955648b | 2020-08-07 17:18:24 | [diff] [blame] | 206 | // Can't negate a __builtin_available condition. But success! |
| 207 | } else { |
Avi Drissman | 9390d28 | 2023-06-26 23:14:49 | [diff] [blame] | 208 | return 15; |
Robert Sesek | 955648b | 2020-08-07 17:18:24 | [diff] [blame] | 209 | } |
| 210 | |
| 211 | return 0; |
| 212 | } |
| 213 | |
| 214 | TEST_F(SandboxMacTest, BuiltinAvailable) { |
| 215 | ExecuteInAllSandboxTypes("BuiltinAvailable", {}); |
| 216 | } |
| 217 | |
Robert Sesek | 6826fb6 | 2020-11-12 22:23:17 | [diff] [blame] | 218 | MULTIPROCESS_TEST_MAIN(NetworkProcessPrefs) { |
| 219 | CheckCreateSeatbeltServer(); |
| 220 | |
Avi Drissman | 370a5810 | 2025-05-06 15:54:54 | [diff] [blame] | 221 | const std::string kBundleId(base::apple::BaseBundleID()); |
Robert Sesek | 6826fb6 | 2020-11-12 22:23:17 | [diff] [blame] | 222 | 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 | |
| 246 | TEST_F(SandboxMacTest, NetworkProcessPrefs) { |
Alex Gough | eb6a38f | 2021-10-22 01:55:13 | [diff] [blame] | 247 | ExecuteWithParams("NetworkProcessPrefs", sandbox::mojom::Sandbox::kNetwork); |
Robert Sesek | 6826fb6 | 2020-11-12 22:23:17 | [diff] [blame] | 248 | } |
| 249 | |
Greg Kerr | 65f314fb | 2018-11-09 20:35:25 | [diff] [blame] | 250 | } // namespace content |