// Copyright (c) 2012 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "base/mac/authorization_util.h" #import #include #include #include #include "base/logging.h" #include "base/mac/bundle_locations.h" #include "base/mac/foundation_util.h" #include "base/mac/mac_logging.h" #include "base/mac/scoped_authorizationref.h" #include "base/posix/eintr_wrapper.h" #include "base/strings/string_number_conversions.h" #include "base/strings/string_util.h" #include "base/threading/hang_watcher.h" namespace base::mac { AuthorizationRef GetAuthorizationRightsWithPrompt( AuthorizationRights* rights, CFStringRef prompt, AuthorizationFlags extraFlags) { // Create an empty AuthorizationRef. ScopedAuthorizationRef authorization; OSStatus status = AuthorizationCreate(NULL, kAuthorizationEmptyEnvironment, kAuthorizationFlagDefaults, authorization.get_pointer()); if (status != errAuthorizationSuccess) { OSSTATUS_LOG(ERROR, status) << "AuthorizationCreate"; return NULL; } // Never consider the current WatchHangsInScope as hung. There was most likely // one created in ThreadControllerWithMessagePumpImpl::DoWork(). The current // hang watching deadline is not valid since the user can take unbounded time // to answer the password prompt. HangWatching will resume when the next task // or event is pumped in MessagePumpCFRunLoop so there is not need to // reactivate it. You can see the function comments for more details. base::HangWatcher::InvalidateActiveExpectations(); AuthorizationFlags flags = kAuthorizationFlagDefaults | kAuthorizationFlagInteractionAllowed | kAuthorizationFlagExtendRights | kAuthorizationFlagPreAuthorize | extraFlags; // product_logo_32.png is used instead of app.icns because Authorization // Services can't deal with .icns files. NSString* icon_path = [base::mac::FrameworkBundle() pathForResource:@"product_logo_32" ofType:@"png"]; const char* icon_path_c = [icon_path fileSystemRepresentation]; size_t icon_path_length = icon_path_c ? strlen(icon_path_c) : 0; // The OS will dispay |prompt| along with a sentence asking the user to type // the "password to allow this." NSString* prompt_ns = base::mac::CFToNSCast(prompt); const char* prompt_c = [prompt_ns UTF8String]; size_t prompt_length = prompt_c ? strlen(prompt_c) : 0; AuthorizationItem environment_items[] = { {kAuthorizationEnvironmentIcon, icon_path_length, (void*)icon_path_c, 0}, {kAuthorizationEnvironmentPrompt, prompt_length, (void*)prompt_c, 0} }; AuthorizationEnvironment environment = {std::size(environment_items), environment_items}; status = AuthorizationCopyRights(authorization, rights, &environment, flags, NULL); if (status != errAuthorizationSuccess) { if (status != errAuthorizationCanceled) { OSSTATUS_LOG(ERROR, status) << "AuthorizationCopyRights"; } return NULL; } return authorization.release(); } AuthorizationRef AuthorizationCreateToRunAsRoot(CFStringRef prompt) { // Specify the "system.privilege.admin" right, which allows // AuthorizationExecuteWithPrivileges to run commands as root. AuthorizationItem right_items[] = { {kAuthorizationRightExecute, 0, NULL, 0} }; AuthorizationRights rights = {std::size(right_items), right_items}; return GetAuthorizationRightsWithPrompt(&rights, prompt, 0); } OSStatus ExecuteWithPrivilegesAndGetPID(AuthorizationRef authorization, const char* tool_path, AuthorizationFlags options, const char** arguments, FILE** pipe, pid_t* pid) { // pipe may be NULL, but this function needs one. In that case, use a local // pipe. FILE* local_pipe; FILE** pipe_pointer; if (pipe) { pipe_pointer = pipe; } else { pipe_pointer = &local_pipe; } // AuthorizationExecuteWithPrivileges is deprecated in macOS 10.7, but no good // replacement exists. https://crbug.com/593133. #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wdeprecated-declarations" // AuthorizationExecuteWithPrivileges wants |char* const*| for |arguments|, // but it doesn't actually modify the arguments, and that type is kind of // silly and callers probably aren't dealing with that. Put the cast here // to make things a little easier on callers. OSStatus status = AuthorizationExecuteWithPrivileges(authorization, tool_path, options, (char* const*)arguments, pipe_pointer); #pragma clang diagnostic pop if (status != errAuthorizationSuccess) { return status; } int line_pid = -1; size_t line_length = 0; char* line_c = fgetln(*pipe_pointer, &line_length); if (line_c) { if (line_length > 0 && line_c[line_length - 1] == '\n') { // line_c + line_length is the start of the next line if there is one. // Back up one character. --line_length; } std::string line(line_c, line_length); if (!base::StringToInt(line, &line_pid)) { // StringToInt may have set line_pid to something, but if the conversion // was imperfect, use -1. LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: funny line: " << line; line_pid = -1; } } else { LOG(ERROR) << "ExecuteWithPrivilegesAndGetPid: no line"; } if (!pipe) { fclose(*pipe_pointer); } if (pid) { *pid = line_pid; } return status; } OSStatus ExecuteWithPrivilegesAndWait(AuthorizationRef authorization, const char* tool_path, AuthorizationFlags options, const char** arguments, FILE** pipe, int* exit_status) { pid_t pid; OSStatus status = ExecuteWithPrivilegesAndGetPID(authorization, tool_path, options, arguments, pipe, &pid); if (status != errAuthorizationSuccess) { return status; } // exit_status may be NULL, but this function needs it. In that case, use a // local version. int local_exit_status; int* exit_status_pointer; if (exit_status) { exit_status_pointer = exit_status; } else { exit_status_pointer = &local_exit_status; } if (pid != -1) { pid_t wait_result = HANDLE_EINTR(waitpid(pid, exit_status_pointer, 0)); if (wait_result != pid) { PLOG(ERROR) << "waitpid"; *exit_status_pointer = -1; } } else { *exit_status_pointer = -1; } return status; } } // namespace base::mac