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