blob: 9a1b30dc2b0b659e819e61c4e58823b65dc58503 [file] [log] [blame]
Nandana Dutt3386fb72018-12-12 17:26:57 +00001/*
2 * Copyright (C) 2019 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.os;
18
19import android.annotation.RequiresPermission;
Nandana Dutt551906c2019-01-23 09:51:49 +000020import android.app.ActivityManager;
Nandana Dutt161a4462019-01-16 18:18:38 +000021import android.app.AppOpsManager;
Nandana Dutt3386fb72018-12-12 17:26:57 +000022import android.content.Context;
Nandana Dutt551906c2019-01-23 09:51:49 +000023import android.content.pm.UserInfo;
Nandana Dutt161a4462019-01-16 18:18:38 +000024import android.os.Binder;
Nandana Dutt3386fb72018-12-12 17:26:57 +000025import android.os.BugreportParams;
26import android.os.IDumpstate;
27import android.os.IDumpstateListener;
Nandana Dutt3386fb72018-12-12 17:26:57 +000028import android.os.RemoteException;
29import android.os.ServiceManager;
30import android.os.SystemClock;
31import android.os.SystemProperties;
Nandana Dutt551906c2019-01-23 09:51:49 +000032import android.os.UserManager;
Nikita Ioffeee4d7be2019-02-28 21:35:02 +000033import android.util.ArraySet;
Nandana Dutt3386fb72018-12-12 17:26:57 +000034import android.util.Slog;
35
Nandana Dutt551906c2019-01-23 09:51:49 +000036import com.android.internal.annotations.GuardedBy;
37import com.android.internal.util.Preconditions;
Nikita Ioffeee4d7be2019-02-28 21:35:02 +000038import com.android.server.SystemConfig;
Nandana Dutt551906c2019-01-23 09:51:49 +000039
Nandana Dutt3386fb72018-12-12 17:26:57 +000040import java.io.FileDescriptor;
41
Nandana Dutt3386fb72018-12-12 17:26:57 +000042/**
43 * Implementation of the service that provides a privileged API to capture and consume bugreports.
44 *
Nandana Dutt551906c2019-01-23 09:51:49 +000045 * <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}.
Nandana Dutt3386fb72018-12-12 17:26:57 +000046 */
47class BugreportManagerServiceImpl extends IDumpstate.Stub {
48 private static final String TAG = "BugreportManagerService";
Nandana Duttb2da22a2019-01-23 08:39:05 +000049 private static final String BUGREPORT_SERVICE = "bugreportd";
Nandana Dutt3386fb72018-12-12 17:26:57 +000050 private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
51
Nandana Dutt551906c2019-01-23 09:51:49 +000052 private final Object mLock = new Object();
Nandana Dutt3386fb72018-12-12 17:26:57 +000053 private final Context mContext;
Nandana Dutt161a4462019-01-16 18:18:38 +000054 private final AppOpsManager mAppOps;
Nikita Ioffeee4d7be2019-02-28 21:35:02 +000055 private final ArraySet<String> mBugreportWhitelistedPackages;
Nandana Dutt3386fb72018-12-12 17:26:57 +000056
57 BugreportManagerServiceImpl(Context context) {
58 mContext = context;
Nandana Dutt161a4462019-01-16 18:18:38 +000059 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Nikita Ioffeee4d7be2019-02-28 21:35:02 +000060 mBugreportWhitelistedPackages =
61 SystemConfig.getInstance().getBugreportWhitelistedPackages();
Nandana Dutt3386fb72018-12-12 17:26:57 +000062 }
63
64 @Override
65 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt161a4462019-01-16 18:18:38 +000066 public void startBugreport(int callingUidUnused, String callingPackage,
67 FileDescriptor bugreportFd, FileDescriptor screenshotFd,
Nandana Dutt551906c2019-01-23 09:51:49 +000068 int bugreportMode, IDumpstateListener listener) {
69 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
70 Preconditions.checkNotNull(callingPackage);
71 Preconditions.checkNotNull(bugreportFd);
72 Preconditions.checkNotNull(listener);
73 validateBugreportMode(bugreportMode);
Abhijeet Kaur9394ea62019-02-28 11:50:27 +000074 final long identity = Binder.clearCallingIdentity();
75 try {
76 ensureIsPrimaryUser();
77 } finally {
78 Binder.restoreCallingIdentity(identity);
79 }
Nandana Dutt3386fb72018-12-12 17:26:57 +000080
Nandana Dutt551906c2019-01-23 09:51:49 +000081 int callingUid = Binder.getCallingUid();
Nandana Dutt161a4462019-01-16 18:18:38 +000082 mAppOps.checkPackage(callingUid, callingPackage);
Nandana Dutt551906c2019-01-23 09:51:49 +000083
Nikita Ioffeee4d7be2019-02-28 21:35:02 +000084 if (!mBugreportWhitelistedPackages.contains(callingPackage)) {
85 throw new SecurityException(
86 callingPackage + " is not whitelisted to use Bugreport API");
87 }
Nandana Dutt551906c2019-01-23 09:51:49 +000088 synchronized (mLock) {
89 startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
90 bugreportMode, listener);
Nandana Dutt3386fb72018-12-12 17:26:57 +000091 }
Nandana Dutt3386fb72018-12-12 17:26:57 +000092 }
93
Nandana Duttb2da22a2019-01-23 08:39:05 +000094 @Override
95 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt551906c2019-01-23 09:51:49 +000096 public void cancelBugreport() {
97 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
98 // This tells init to cancel bugreportd service. Note that this is achieved through setting
99 // a system property which is not thread-safe. So the lock here offers thread-safety only
100 // among callers of the API.
101 synchronized (mLock) {
102 SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
103 }
Nandana Duttb2da22a2019-01-23 08:39:05 +0000104 }
105
Nandana Dutt551906c2019-01-23 09:51:49 +0000106 private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000107 if (mode != BugreportParams.BUGREPORT_MODE_FULL
108 && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
109 && mode != BugreportParams.BUGREPORT_MODE_REMOTE
110 && mode != BugreportParams.BUGREPORT_MODE_WEAR
111 && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
112 && mode != BugreportParams.BUGREPORT_MODE_WIFI) {
113 Slog.w(TAG, "Unknown bugreport mode: " + mode);
Nandana Dutt551906c2019-01-23 09:51:49 +0000114 throw new IllegalArgumentException("Unknown bugreport mode: " + mode);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000115 }
Nandana Dutt551906c2019-01-23 09:51:49 +0000116 }
117
118 /**
119 * Validates that the current user is the primary user.
120 *
121 * @throws IllegalArgumentException if the current user is not the primary user
122 */
123 private void ensureIsPrimaryUser() {
124 UserInfo currentUser = null;
125 try {
126 currentUser = ActivityManager.getService().getCurrentUser();
127 } catch (RemoteException e) {
128 // Impossible to get RemoteException for an in-process call.
129 }
130
131 UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser();
132 if (currentUser == null) {
133 logAndThrow("No current user. Only primary user is allowed to take bugreports.");
134 }
135 if (primaryUser == null) {
136 logAndThrow("No primary user. Only primary user is allowed to take bugreports.");
137 }
138 if (primaryUser.id != currentUser.id) {
139 logAndThrow("Current user not primary user. Only primary user"
140 + " is allowed to take bugreports.");
141 }
142 }
143
144 @GuardedBy("mLock")
145 private void startBugreportLocked(int callingUid, String callingPackage,
146 FileDescriptor bugreportFd, FileDescriptor screenshotFd,
147 int bugreportMode, IDumpstateListener listener) {
148 if (isDumpstateBinderServiceRunningLocked()) {
149 Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
150 + " while another one is currently in progress.");
Nandana Duttcfb3d482019-02-20 11:25:35 +0000151 reportError(listener,
152 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
Nandana Dutt551906c2019-01-23 09:51:49 +0000153 return;
154 }
155
156 IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
157 if (ds == null) {
158 Slog.w(TAG, "Unable to get bugreport service");
159 reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
160 return;
161 }
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000162
163 // Wrap the listener so we can intercept binder events directly.
164 IDumpstateListener myListener = new DumpstateListener(listener, ds);
Nandana Dutt551906c2019-01-23 09:51:49 +0000165 try {
166 ds.startBugreport(callingUid, callingPackage,
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000167 bugreportFd, screenshotFd, bugreportMode, myListener);
Nandana Dutt551906c2019-01-23 09:51:49 +0000168 } catch (RemoteException e) {
Nandana Dutt664f6462019-03-22 14:48:50 +0000169 // bugreportd service is already started now. We need to kill it to manage the
170 // lifecycle correctly. If we don't subsequent callers will get
171 // BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS error.
172 // Note that listener will be notified by the death recipient below.
173 cancelBugreport();
Nandana Dutt551906c2019-01-23 09:51:49 +0000174 }
175 }
176
177 @GuardedBy("mLock")
178 private boolean isDumpstateBinderServiceRunningLocked() {
179 IDumpstate ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
180 return ds != null;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000181 }
182
183 /*
184 * Start and get a handle to the native implementation of {@code IDumpstate} which does the
185 * actual bugreport generation.
186 *
187 * <p>Generating bugreports requires root privileges. To limit the footprint
188 * of the root access, the actual generation in Dumpstate binary is accessed as a
189 * oneshot service 'bugreport'.
Nandana Dutt551906c2019-01-23 09:51:49 +0000190 *
191 * <p>Note that starting the service is achieved through setting a system property, which is
192 * not thread-safe. So the lock here offers thread-safety only among callers of the API.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000193 */
Nandana Dutt551906c2019-01-23 09:51:49 +0000194 @GuardedBy("mLock")
195 private IDumpstate startAndGetDumpstateBinderServiceLocked() {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000196 // Start bugreport service.
Nandana Duttb2da22a2019-01-23 08:39:05 +0000197 SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000198
199 IDumpstate ds = null;
200 boolean timedOut = false;
201 int totalTimeWaitedMillis = 0;
202 int seedWaitTimeMillis = 500;
203 while (!timedOut) {
204 // Note that the binder service on the native side is "dumpstate".
205 ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
206 if (ds != null) {
207 Slog.i(TAG, "Got bugreport service handle.");
208 break;
209 }
210 SystemClock.sleep(seedWaitTimeMillis);
211 Slog.i(TAG,
212 "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
213 totalTimeWaitedMillis += seedWaitTimeMillis;
214 seedWaitTimeMillis *= 2;
215 timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
216 }
217 if (timedOut) {
218 Slog.w(TAG,
219 "Timed out waiting to get dumpstate service handle ("
220 + totalTimeWaitedMillis + "ms)");
221 }
222 return ds;
223 }
Nandana Dutt551906c2019-01-23 09:51:49 +0000224
225 private void reportError(IDumpstateListener listener, int errorCode) {
226 try {
227 listener.onError(errorCode);
228 } catch (RemoteException e) {
229 // Something went wrong in binder or app process. There's nothing to do here.
230 Slog.w(TAG, "onError() transaction threw RemoteException: " + e.getMessage());
231 }
232 }
233
234 private void logAndThrow(String message) {
235 Slog.w(TAG, message);
236 throw new IllegalArgumentException(message);
237 }
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000238
239
240 private final class DumpstateListener extends IDumpstateListener.Stub
241 implements DeathRecipient {
242 private final IDumpstateListener mListener;
243 private final IDumpstate mDs;
244 private boolean mDone = false;
245
246 DumpstateListener(IDumpstateListener listener, IDumpstate ds) {
247 mListener = listener;
248 mDs = ds;
249 try {
250 mDs.asBinder().linkToDeath(this, 0);
251 } catch (RemoteException e) {
252 Slog.e(TAG, "Unable to register Death Recipient for IDumpstate", e);
253 }
254 }
255
256 @Override
257 public void onProgress(int progress) throws RemoteException {
258 mListener.onProgress(progress);
259 }
260
261 @Override
262 public void onError(int errorCode) throws RemoteException {
263 synchronized (mLock) {
264 mDone = true;
265 }
266 mListener.onError(errorCode);
267 }
268
269 @Override
270 public void onFinished() throws RemoteException {
271 synchronized (mLock) {
272 mDone = true;
273 }
274 mListener.onFinished();
275 }
276
277 @Override
278 public void binderDied() {
279 synchronized (mLock) {
280 if (!mDone) {
281 // If we have not gotten a "done" callback this must be a crash.
282 Slog.e(TAG, "IDumpstate likely crashed. Notifying listener");
283 try {
284 mListener.onError(IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
285 } catch (RemoteException ignored) {
286 // If listener is not around, there isn't anything to do here.
287 }
288 }
289 }
290 mDs.asBinder().unlinkToDeath(this, 0);
291 }
Nandana Dutt9d6ff292019-03-13 16:42:17 +0000292 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000293}