blob: 85a3ba1a85592fbde14086adf4ce781281f80227 [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;
28import android.os.IDumpstateToken;
29import android.os.RemoteException;
30import android.os.ServiceManager;
31import android.os.SystemClock;
32import android.os.SystemProperties;
Nandana Dutt551906c2019-01-23 09:51:49 +000033import android.os.UserManager;
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;
38
Nandana Dutt3386fb72018-12-12 17:26:57 +000039import java.io.FileDescriptor;
40
41// TODO(b/111441001):
Nandana Dutt551906c2019-01-23 09:51:49 +000042// Intercept onFinished() & implement death recipient here and shutdown
43// bugreportd service.
Nandana Dutt3386fb72018-12-12 17:26:57 +000044
45/**
46 * Implementation of the service that provides a privileged API to capture and consume bugreports.
47 *
Nandana Dutt551906c2019-01-23 09:51:49 +000048 * <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}.
Nandana Dutt3386fb72018-12-12 17:26:57 +000049 */
50class BugreportManagerServiceImpl extends IDumpstate.Stub {
51 private static final String TAG = "BugreportManagerService";
Nandana Duttb2da22a2019-01-23 08:39:05 +000052 private static final String BUGREPORT_SERVICE = "bugreportd";
Nandana Dutt3386fb72018-12-12 17:26:57 +000053 private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
54
Nandana Dutt551906c2019-01-23 09:51:49 +000055 private final Object mLock = new Object();
Nandana Dutt3386fb72018-12-12 17:26:57 +000056 private final Context mContext;
Nandana Dutt161a4462019-01-16 18:18:38 +000057 private final AppOpsManager mAppOps;
Nandana Dutt3386fb72018-12-12 17:26:57 +000058
59 BugreportManagerServiceImpl(Context context) {
60 mContext = context;
Nandana Dutt161a4462019-01-16 18:18:38 +000061 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Nandana Dutt3386fb72018-12-12 17:26:57 +000062 }
63
64 @Override
65 @RequiresPermission(android.Manifest.permission.DUMP)
66 public IDumpstateToken setListener(String name, IDumpstateListener listener,
Nandana Dutt551906c2019-01-23 09:51:49 +000067 boolean getSectionDetails) {
Nandana Dutt3386fb72018-12-12 17:26:57 +000068 throw new UnsupportedOperationException("setListener is not allowed on this service");
69 }
70
Nandana Dutt3386fb72018-12-12 17:26:57 +000071 @Override
72 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt161a4462019-01-16 18:18:38 +000073 public void startBugreport(int callingUidUnused, String callingPackage,
74 FileDescriptor bugreportFd, FileDescriptor screenshotFd,
Nandana Dutt551906c2019-01-23 09:51:49 +000075 int bugreportMode, IDumpstateListener listener) {
76 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
77 Preconditions.checkNotNull(callingPackage);
78 Preconditions.checkNotNull(bugreportFd);
79 Preconditions.checkNotNull(listener);
80 validateBugreportMode(bugreportMode);
Abhijeet Kaur9394ea62019-02-28 11:50:27 +000081 final long identity = Binder.clearCallingIdentity();
82 try {
83 ensureIsPrimaryUser();
84 } finally {
85 Binder.restoreCallingIdentity(identity);
86 }
Nandana Dutt3386fb72018-12-12 17:26:57 +000087
Nandana Dutt551906c2019-01-23 09:51:49 +000088 int callingUid = Binder.getCallingUid();
Nandana Dutt161a4462019-01-16 18:18:38 +000089 mAppOps.checkPackage(callingUid, callingPackage);
Nandana Dutt551906c2019-01-23 09:51:49 +000090
91 synchronized (mLock) {
92 startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
93 bugreportMode, listener);
Nandana Dutt3386fb72018-12-12 17:26:57 +000094 }
Nandana Dutt3386fb72018-12-12 17:26:57 +000095 }
96
Nandana Duttb2da22a2019-01-23 08:39:05 +000097 @Override
98 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt551906c2019-01-23 09:51:49 +000099 public void cancelBugreport() {
100 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
101 // This tells init to cancel bugreportd service. Note that this is achieved through setting
102 // a system property which is not thread-safe. So the lock here offers thread-safety only
103 // among callers of the API.
104 synchronized (mLock) {
105 SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
106 }
Nandana Duttb2da22a2019-01-23 08:39:05 +0000107 }
108
Nandana Dutt551906c2019-01-23 09:51:49 +0000109 private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000110 if (mode != BugreportParams.BUGREPORT_MODE_FULL
111 && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
112 && mode != BugreportParams.BUGREPORT_MODE_REMOTE
113 && mode != BugreportParams.BUGREPORT_MODE_WEAR
114 && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
115 && mode != BugreportParams.BUGREPORT_MODE_WIFI) {
116 Slog.w(TAG, "Unknown bugreport mode: " + mode);
Nandana Dutt551906c2019-01-23 09:51:49 +0000117 throw new IllegalArgumentException("Unknown bugreport mode: " + mode);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000118 }
Nandana Dutt551906c2019-01-23 09:51:49 +0000119 }
120
121 /**
122 * Validates that the current user is the primary user.
123 *
124 * @throws IllegalArgumentException if the current user is not the primary user
125 */
126 private void ensureIsPrimaryUser() {
127 UserInfo currentUser = null;
128 try {
129 currentUser = ActivityManager.getService().getCurrentUser();
130 } catch (RemoteException e) {
131 // Impossible to get RemoteException for an in-process call.
132 }
133
134 UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser();
135 if (currentUser == null) {
136 logAndThrow("No current user. Only primary user is allowed to take bugreports.");
137 }
138 if (primaryUser == null) {
139 logAndThrow("No primary user. Only primary user is allowed to take bugreports.");
140 }
141 if (primaryUser.id != currentUser.id) {
142 logAndThrow("Current user not primary user. Only primary user"
143 + " is allowed to take bugreports.");
144 }
145 }
146
147 @GuardedBy("mLock")
148 private void startBugreportLocked(int callingUid, String callingPackage,
149 FileDescriptor bugreportFd, FileDescriptor screenshotFd,
150 int bugreportMode, IDumpstateListener listener) {
151 if (isDumpstateBinderServiceRunningLocked()) {
152 Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
153 + " while another one is currently in progress.");
Nandana Duttcfb3d482019-02-20 11:25:35 +0000154 reportError(listener,
155 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
Nandana Dutt551906c2019-01-23 09:51:49 +0000156 return;
157 }
158
159 IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
160 if (ds == null) {
161 Slog.w(TAG, "Unable to get bugreport service");
162 reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
163 return;
164 }
165 try {
166 ds.startBugreport(callingUid, callingPackage,
167 bugreportFd, screenshotFd, bugreportMode, listener);
168 } catch (RemoteException e) {
169 reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
170 }
171 }
172
173 @GuardedBy("mLock")
174 private boolean isDumpstateBinderServiceRunningLocked() {
175 IDumpstate ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
176 return ds != null;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000177 }
178
179 /*
180 * Start and get a handle to the native implementation of {@code IDumpstate} which does the
181 * actual bugreport generation.
182 *
183 * <p>Generating bugreports requires root privileges. To limit the footprint
184 * of the root access, the actual generation in Dumpstate binary is accessed as a
185 * oneshot service 'bugreport'.
Nandana Dutt551906c2019-01-23 09:51:49 +0000186 *
187 * <p>Note that starting the service is achieved through setting a system property, which is
188 * not thread-safe. So the lock here offers thread-safety only among callers of the API.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000189 */
Nandana Dutt551906c2019-01-23 09:51:49 +0000190 @GuardedBy("mLock")
191 private IDumpstate startAndGetDumpstateBinderServiceLocked() {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000192 // Start bugreport service.
Nandana Duttb2da22a2019-01-23 08:39:05 +0000193 SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000194
195 IDumpstate ds = null;
196 boolean timedOut = false;
197 int totalTimeWaitedMillis = 0;
198 int seedWaitTimeMillis = 500;
199 while (!timedOut) {
200 // Note that the binder service on the native side is "dumpstate".
201 ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
202 if (ds != null) {
203 Slog.i(TAG, "Got bugreport service handle.");
204 break;
205 }
206 SystemClock.sleep(seedWaitTimeMillis);
207 Slog.i(TAG,
208 "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
209 totalTimeWaitedMillis += seedWaitTimeMillis;
210 seedWaitTimeMillis *= 2;
211 timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
212 }
213 if (timedOut) {
214 Slog.w(TAG,
215 "Timed out waiting to get dumpstate service handle ("
216 + totalTimeWaitedMillis + "ms)");
217 }
218 return ds;
219 }
Nandana Dutt551906c2019-01-23 09:51:49 +0000220
221 private void reportError(IDumpstateListener listener, int errorCode) {
222 try {
223 listener.onError(errorCode);
224 } catch (RemoteException e) {
225 // Something went wrong in binder or app process. There's nothing to do here.
226 Slog.w(TAG, "onError() transaction threw RemoteException: " + e.getMessage());
227 }
228 }
229
230 private void logAndThrow(String message) {
231 Slog.w(TAG, message);
232 throw new IllegalArgumentException(message);
233 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000234}