Avi Drissman | e4622aa | 2022-09-08 20:36:06 | [diff] [blame] | 1 | // Copyright 2012 The Chromium Authors |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [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 | |
danakj | 51d26a4 | 2024-04-25 14:23:56 | [diff] [blame] | 5 | #ifdef UNSAFE_BUFFERS_BUILD |
| 6 | // TODO(crbug.com/40284755): Remove this and spanify to fix the errors. |
| 7 | #pragma allow_unsafe_buffers |
| 8 | #endif |
| 9 | |
[email protected] | ef12d1e6 | 2012-03-21 20:55:05 | [diff] [blame] | 10 | #include "base/mac/authorization_util.h" |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 11 | |
| 12 | #import <Foundation/Foundation.h> |
avi | dbcc841 | 2015-12-24 06:28:32 | [diff] [blame] | 13 | #include <stddef.h> |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 14 | #include <sys/wait.h> |
| 15 | |
| 16 | #include <string> |
| 17 | |
Avi Drissman | d4f0708 | 2023-05-12 18:05:44 | [diff] [blame] | 18 | #include "base/apple/bundle_locations.h" |
Avi Drissman | eac566b0 | 2023-08-18 02:56:21 | [diff] [blame] | 19 | #include "base/apple/foundation_util.h" |
Avi Drissman | 280c58a | 2023-08-16 16:27:55 | [diff] [blame] | 20 | #include "base/apple/osstatus_logging.h" |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 21 | #include "base/logging.h" |
[email protected] | ef12d1e6 | 2012-03-21 20:55:05 | [diff] [blame] | 22 | #include "base/mac/scoped_authorizationref.h" |
[email protected] | 2025d00 | 2012-11-14 20:54:35 | [diff] [blame] | 23 | #include "base/posix/eintr_wrapper.h" |
[email protected] | 3ea1b18 | 2013-02-08 22:38:41 | [diff] [blame] | 24 | #include "base/strings/string_number_conversions.h" |
[email protected] | d529cb0 | 2013-06-10 19:06:57 | [diff] [blame] | 25 | #include "base/strings/string_util.h" |
Avi Drissman | 21e25b1 | 2023-06-22 14:30:57 | [diff] [blame] | 26 | #include "base/strings/sys_string_conversions.h" |
Olivier Li | 9646c09f | 2021-08-09 19:12:55 | [diff] [blame] | 27 | #include "base/threading/hang_watcher.h" |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 28 | |
Avi Drissman | efca412 | 2022-01-05 23:59:36 | [diff] [blame] | 29 | namespace base::mac { |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 30 | |
Mark Rowe | 463ae62 | 2023-02-14 23:51:57 | [diff] [blame] | 31 | ScopedAuthorizationRef CreateAuthorization() { |
[email protected] | a6e349a6 | 2011-06-27 16:26:24 | [diff] [blame] | 32 | ScopedAuthorizationRef authorization; |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 33 | OSStatus status = AuthorizationCreate( |
| 34 | /*rights=*/nullptr, kAuthorizationEmptyEnvironment, |
| 35 | kAuthorizationFlagDefaults, authorization.InitializeInto()); |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 36 | if (status != errAuthorizationSuccess) { |
[email protected] | 33b9820 | 2012-01-27 23:06:49 | [diff] [blame] | 37 | OSSTATUS_LOG(ERROR, status) << "AuthorizationCreate"; |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 38 | return ScopedAuthorizationRef(); |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 39 | } |
| 40 | |
Mark Rowe | 463ae62 | 2023-02-14 23:51:57 | [diff] [blame] | 41 | return authorization; |
| 42 | } |
| 43 | |
| 44 | ScopedAuthorizationRef GetAuthorizationRightsWithPrompt( |
| 45 | AuthorizationRights* rights, |
| 46 | CFStringRef prompt, |
| 47 | AuthorizationFlags extra_flags) { |
| 48 | ScopedAuthorizationRef authorization = CreateAuthorization(); |
Mark Rowe | 31692c6 | 2023-02-16 12:05:01 | [diff] [blame] | 49 | if (!authorization) { |
Mark Rowe | 463ae62 | 2023-02-14 23:51:57 | [diff] [blame] | 50 | return authorization; |
| 51 | } |
| 52 | |
Olivier Li | 9646c09f | 2021-08-09 19:12:55 | [diff] [blame] | 53 | // Never consider the current WatchHangsInScope as hung. There was most likely |
| 54 | // one created in ThreadControllerWithMessagePumpImpl::DoWork(). The current |
| 55 | // hang watching deadline is not valid since the user can take unbounded time |
| 56 | // to answer the password prompt. HangWatching will resume when the next task |
| 57 | // or event is pumped in MessagePumpCFRunLoop so there is not need to |
| 58 | // reactivate it. You can see the function comments for more details. |
| 59 | base::HangWatcher::InvalidateActiveExpectations(); |
| 60 | |
[email protected] | 429a6e0 | 2013-10-23 18:40:48 | [diff] [blame] | 61 | AuthorizationFlags flags = kAuthorizationFlagDefaults | |
| 62 | kAuthorizationFlagInteractionAllowed | |
| 63 | kAuthorizationFlagExtendRights | |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 64 | kAuthorizationFlagPreAuthorize | extra_flags; |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 65 | |
| 66 | // product_logo_32.png is used instead of app.icns because Authorization |
| 67 | // Services can't deal with .icns files. |
| 68 | NSString* icon_path = |
Avi Drissman | d4f0708 | 2023-05-12 18:05:44 | [diff] [blame] | 69 | [base::apple::FrameworkBundle() pathForResource:@"product_logo_32" |
| 70 | ofType:@"png"]; |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 71 | const char* icon_path_c = [icon_path fileSystemRepresentation]; |
| 72 | size_t icon_path_length = icon_path_c ? strlen(icon_path_c) : 0; |
| 73 | |
Avi Drissman | 6fdbaf08 | 2023-06-07 19:35:09 | [diff] [blame] | 74 | // The OS will display |prompt| along with a sentence asking the user to type |
vabr | a7553ab | 2015-10-16 08:48:10 | [diff] [blame] | 75 | // the "password to allow this." |
Avi Drissman | cadb19a0 | 2023-07-25 20:46:27 | [diff] [blame] | 76 | std::string prompt_string; |
Avi Drissman | 21e25b1 | 2023-06-22 14:30:57 | [diff] [blame] | 77 | const char* prompt_c = nullptr; |
| 78 | size_t prompt_length = 0; |
| 79 | if (prompt) { |
Avi Drissman | cadb19a0 | 2023-07-25 20:46:27 | [diff] [blame] | 80 | prompt_string = SysCFStringRefToUTF8(prompt); |
Avi Drissman | 21e25b1 | 2023-06-22 14:30:57 | [diff] [blame] | 81 | prompt_c = prompt_string.c_str(); |
| 82 | prompt_length = prompt_string.length(); |
| 83 | } |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 84 | |
| 85 | AuthorizationItem environment_items[] = { |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 86 | {kAuthorizationEnvironmentIcon, icon_path_length, (void*)icon_path_c, 0}, |
| 87 | {kAuthorizationEnvironmentPrompt, prompt_length, (void*)prompt_c, 0}}; |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 88 | |
Daniel Cheng | f45f4760 | 2022-02-28 22:38:32 | [diff] [blame] | 89 | AuthorizationEnvironment environment = {std::size(environment_items), |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 90 | environment_items}; |
| 91 | |
Mark Rowe | 463ae62 | 2023-02-14 23:51:57 | [diff] [blame] | 92 | OSStatus status = AuthorizationCopyRights(authorization, rights, &environment, |
| 93 | flags, nullptr); |
[email protected] | 429a6e0 | 2013-10-23 18:40:48 | [diff] [blame] | 94 | |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 95 | if (status != errAuthorizationSuccess) { |
| 96 | if (status != errAuthorizationCanceled) { |
[email protected] | 33b9820 | 2012-01-27 23:06:49 | [diff] [blame] | 97 | OSSTATUS_LOG(ERROR, status) << "AuthorizationCopyRights"; |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 98 | } |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 99 | return ScopedAuthorizationRef(); |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 100 | } |
| 101 | |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 102 | return authorization; |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 103 | } |
| 104 | |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 105 | ScopedAuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { |
[email protected] | 429a6e0 | 2013-10-23 18:40:48 | [diff] [blame] | 106 | // Specify the "system.privilege.admin" right, which allows |
| 107 | // AuthorizationExecuteWithPrivileges to run commands as root. |
| 108 | AuthorizationItem right_items[] = { |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 109 | {kAuthorizationRightExecute, 0, nullptr, 0}}; |
Daniel Cheng | f45f4760 | 2022-02-28 22:38:32 | [diff] [blame] | 110 | AuthorizationRights rights = {std::size(right_items), right_items}; |
[email protected] | 429a6e0 | 2013-10-23 18:40:48 | [diff] [blame] | 111 | |
Avi Drissman | 70141bfa | 2022-12-30 20:52:58 | [diff] [blame] | 112 | return GetAuthorizationRightsWithPrompt(&rights, prompt, /*extra_flags=*/0); |
[email protected] | 429a6e0 | 2013-10-23 18:40:48 | [diff] [blame] | 113 | } |
| 114 | |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 115 | OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization, |
| 116 | const char* tool_path, |
| 117 | AuthorizationFlags options, |
| 118 | const char** arguments, |
| 119 | FILE** pipe, |
| 120 | pid_t* pid) { |
| 121 | // pipe may be NULL, but this function needs one. In that case, use a local |
| 122 | // pipe. |
| 123 | FILE* local_pipe; |
| 124 | FILE** pipe_pointer; |
| 125 | if (pipe) { |
| 126 | pipe_pointer = pipe; |
| 127 | } else { |
| 128 | pipe_pointer = &local_pipe; |
| 129 | } |
| 130 | |
erikchen | 1a2f872 | 2016-10-05 16:12:51 | [diff] [blame] | 131 | // AuthorizationExecuteWithPrivileges is deprecated in macOS 10.7, but no good |
| 132 | // replacement exists. https://crbug.com/593133. |
erikchen | bd736b0 | 2016-03-08 23:47:39 | [diff] [blame] | 133 | #pragma clang diagnostic push |
| 134 | #pragma clang diagnostic ignored "-Wdeprecated-declarations" |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 135 | // AuthorizationExecuteWithPrivileges wants |char* const*| for |arguments|, |
| 136 | // but it doesn't actually modify the arguments, and that type is kind of |
| 137 | // silly and callers probably aren't dealing with that. Put the cast here |
| 138 | // to make things a little easier on callers. |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 139 | OSStatus status = AuthorizationExecuteWithPrivileges( |
| 140 | authorization, tool_path, options, (char* const*)arguments, pipe_pointer); |
erikchen | bd736b0 | 2016-03-08 23:47:39 | [diff] [blame] | 141 | #pragma clang diagnostic pop |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 142 | if (status != errAuthorizationSuccess) { |
| 143 | return status; |
| 144 | } |
| 145 | |
| 146 | int line_pid = -1; |
| 147 | size_t line_length = 0; |
| 148 | char* line_c = fgetln(*pipe_pointer, &line_length); |
| 149 | if (line_c) { |
| 150 | if (line_length > 0 && line_c[line_length - 1] == '\n') { |
| 151 | // line_c + line_length is the start of the next line if there is one. |
| 152 | // Back up one character. |
| 153 | --line_length; |
| 154 | } |
| 155 | std::string line(line_c, line_length); |
| 156 | if (!base::StringToInt(line, &line_pid)) { |
| 157 | // StringToInt may have set line_pid to something, but if the conversion |
| 158 | // was imperfect, use -1. |
| 159 | LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: funny line: " << line; |
| 160 | line_pid = -1; |
| 161 | } |
| 162 | } else { |
| 163 | LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: no line"; |
| 164 | } |
| 165 | |
| 166 | if (!pipe) { |
| 167 | fclose(*pipe_pointer); |
| 168 | } |
| 169 | |
| 170 | if (pid) { |
| 171 | *pid = line_pid; |
| 172 | } |
| 173 | |
| 174 | return status; |
| 175 | } |
| 176 | |
| 177 | OSStatus ExecuteWithPrivilegesAndWait(AuthorizationRef authorization, |
| 178 | const char* tool_path, |
| 179 | AuthorizationFlags options, |
| 180 | const char** arguments, |
| 181 | FILE** pipe, |
| 182 | int* exit_status) { |
| 183 | pid_t pid; |
Peter Kasting | 134ef9af | 2024-12-28 02:30:09 | [diff] [blame] | 184 | OSStatus status = ExecuteWithPrivilegesAndGetPID( |
| 185 | authorization, tool_path, options, arguments, pipe, &pid); |
[email protected] | 7d79165 | 2010-12-01 16:34:49 | [diff] [blame] | 186 | if (status != errAuthorizationSuccess) { |
| 187 | return status; |
| 188 | } |
| 189 | |
| 190 | // exit_status may be NULL, but this function needs it. In that case, use a |
| 191 | // local version. |
| 192 | int local_exit_status; |
| 193 | int* exit_status_pointer; |
| 194 | if (exit_status) { |
| 195 | exit_status_pointer = exit_status; |
| 196 | } else { |
| 197 | exit_status_pointer = &local_exit_status; |
| 198 | } |
| 199 | |
| 200 | if (pid != -1) { |
| 201 | pid_t wait_result = HANDLE_EINTR(waitpid(pid, exit_status_pointer, 0)); |
| 202 | if (wait_result != pid) { |
| 203 | PLOG(ERROR) << "waitpid"; |
| 204 | *exit_status_pointer = -1; |
| 205 | } |
| 206 | } else { |
| 207 | *exit_status_pointer = -1; |
| 208 | } |
| 209 | |
| 210 | return status; |
| 211 | } |
| 212 | |
Avi Drissman | efca412 | 2022-01-05 23:59:36 | [diff] [blame] | 213 | } // namespace base::mac |