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