xref: /aosp_15_r20/external/cronet/base/mac/authorization_util.mm (revision 6777b5387eb2ff775bb5750e3f5d96f37fb7352b)
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