blob: 64f31cd2b11949d909aaafc2f9802269fafea9a3 [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;
Nikita Ioffe07964b42019-02-28 21:35:02 +000034import android.util.ArraySet;
Nandana Dutt3386fb72018-12-12 17:26:57 +000035import android.util.Slog;
36
Nandana Dutt551906c2019-01-23 09:51:49 +000037import com.android.internal.annotations.GuardedBy;
38import com.android.internal.util.Preconditions;
Nikita Ioffe07964b42019-02-28 21:35:02 +000039import com.android.server.SystemConfig;
Nandana Dutt551906c2019-01-23 09:51:49 +000040
Nandana Dutt3386fb72018-12-12 17:26:57 +000041import java.io.FileDescriptor;
42
43// TODO(b/111441001):
Nandana Dutt551906c2019-01-23 09:51:49 +000044// Intercept onFinished() & implement death recipient here and shutdown
45// bugreportd service.
Nandana Dutt3386fb72018-12-12 17:26:57 +000046
47/**
48 * Implementation of the service that provides a privileged API to capture and consume bugreports.
49 *
Nandana Dutt551906c2019-01-23 09:51:49 +000050 * <p>Delegates the actualy generation to a native implementation of {@code IDumpstate}.
Nandana Dutt3386fb72018-12-12 17:26:57 +000051 */
52class BugreportManagerServiceImpl extends IDumpstate.Stub {
53 private static final String TAG = "BugreportManagerService";
Nandana Duttb2da22a2019-01-23 08:39:05 +000054 private static final String BUGREPORT_SERVICE = "bugreportd";
Nandana Dutt3386fb72018-12-12 17:26:57 +000055 private static final long DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS = 30 * 1000;
56
Nandana Dutt551906c2019-01-23 09:51:49 +000057 private final Object mLock = new Object();
Nandana Dutt3386fb72018-12-12 17:26:57 +000058 private final Context mContext;
Nandana Dutt161a4462019-01-16 18:18:38 +000059 private final AppOpsManager mAppOps;
Nikita Ioffe07964b42019-02-28 21:35:02 +000060 private final ArraySet<String> mBugreportWhitelistedPackages;
Nandana Dutt3386fb72018-12-12 17:26:57 +000061
62 BugreportManagerServiceImpl(Context context) {
63 mContext = context;
Nandana Dutt161a4462019-01-16 18:18:38 +000064 mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
Nikita Ioffe07964b42019-02-28 21:35:02 +000065 mBugreportWhitelistedPackages =
66 SystemConfig.getInstance().getBugreportWhitelistedPackages();
Nandana Dutt3386fb72018-12-12 17:26:57 +000067 }
68
69 @Override
70 @RequiresPermission(android.Manifest.permission.DUMP)
71 public IDumpstateToken setListener(String name, IDumpstateListener listener,
Nandana Dutt551906c2019-01-23 09:51:49 +000072 boolean getSectionDetails) {
Nandana Dutt3386fb72018-12-12 17:26:57 +000073 throw new UnsupportedOperationException("setListener is not allowed on this service");
74 }
75
Nandana Dutt3386fb72018-12-12 17:26:57 +000076 @Override
77 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt161a4462019-01-16 18:18:38 +000078 public void startBugreport(int callingUidUnused, String callingPackage,
79 FileDescriptor bugreportFd, FileDescriptor screenshotFd,
Nandana Dutt551906c2019-01-23 09:51:49 +000080 int bugreportMode, IDumpstateListener listener) {
81 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
82 Preconditions.checkNotNull(callingPackage);
83 Preconditions.checkNotNull(bugreportFd);
84 Preconditions.checkNotNull(listener);
85 validateBugreportMode(bugreportMode);
Abhijeet Kaurcc56417a2019-02-28 11:50:27 +000086 final long identity = Binder.clearCallingIdentity();
87 try {
88 ensureIsPrimaryUser();
89 } finally {
90 Binder.restoreCallingIdentity(identity);
91 }
Nandana Dutt3386fb72018-12-12 17:26:57 +000092
Nandana Dutt551906c2019-01-23 09:51:49 +000093 int callingUid = Binder.getCallingUid();
Nandana Dutt161a4462019-01-16 18:18:38 +000094 mAppOps.checkPackage(callingUid, callingPackage);
Nandana Dutt551906c2019-01-23 09:51:49 +000095
Nikita Ioffe07964b42019-02-28 21:35:02 +000096 if (!mBugreportWhitelistedPackages.contains(callingPackage)) {
97 throw new SecurityException(
98 callingPackage + " is not whitelisted to use Bugreport API");
99 }
Nandana Dutt551906c2019-01-23 09:51:49 +0000100 synchronized (mLock) {
101 startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
102 bugreportMode, listener);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000103 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000104 }
105
Nandana Duttb2da22a2019-01-23 08:39:05 +0000106 @Override
107 @RequiresPermission(android.Manifest.permission.DUMP)
Nandana Dutt551906c2019-01-23 09:51:49 +0000108 public void cancelBugreport() {
109 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
110 // This tells init to cancel bugreportd service. Note that this is achieved through setting
111 // a system property which is not thread-safe. So the lock here offers thread-safety only
112 // among callers of the API.
113 synchronized (mLock) {
114 SystemProperties.set("ctl.stop", BUGREPORT_SERVICE);
115 }
Nandana Duttb2da22a2019-01-23 08:39:05 +0000116 }
117
Nandana Dutt551906c2019-01-23 09:51:49 +0000118 private void validateBugreportMode(@BugreportParams.BugreportMode int mode) {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000119 if (mode != BugreportParams.BUGREPORT_MODE_FULL
120 && mode != BugreportParams.BUGREPORT_MODE_INTERACTIVE
121 && mode != BugreportParams.BUGREPORT_MODE_REMOTE
122 && mode != BugreportParams.BUGREPORT_MODE_WEAR
123 && mode != BugreportParams.BUGREPORT_MODE_TELEPHONY
124 && mode != BugreportParams.BUGREPORT_MODE_WIFI) {
125 Slog.w(TAG, "Unknown bugreport mode: " + mode);
Nandana Dutt551906c2019-01-23 09:51:49 +0000126 throw new IllegalArgumentException("Unknown bugreport mode: " + mode);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000127 }
Nandana Dutt551906c2019-01-23 09:51:49 +0000128 }
129
130 /**
131 * Validates that the current user is the primary user.
132 *
133 * @throws IllegalArgumentException if the current user is not the primary user
134 */
135 private void ensureIsPrimaryUser() {
136 UserInfo currentUser = null;
137 try {
138 currentUser = ActivityManager.getService().getCurrentUser();
139 } catch (RemoteException e) {
140 // Impossible to get RemoteException for an in-process call.
141 }
142
143 UserInfo primaryUser = UserManager.get(mContext).getPrimaryUser();
144 if (currentUser == null) {
145 logAndThrow("No current user. Only primary user is allowed to take bugreports.");
146 }
147 if (primaryUser == null) {
148 logAndThrow("No primary user. Only primary user is allowed to take bugreports.");
149 }
150 if (primaryUser.id != currentUser.id) {
151 logAndThrow("Current user not primary user. Only primary user"
152 + " is allowed to take bugreports.");
153 }
154 }
155
156 @GuardedBy("mLock")
157 private void startBugreportLocked(int callingUid, String callingPackage,
158 FileDescriptor bugreportFd, FileDescriptor screenshotFd,
159 int bugreportMode, IDumpstateListener listener) {
160 if (isDumpstateBinderServiceRunningLocked()) {
161 Slog.w(TAG, "'dumpstate' is already running. Cannot start a new bugreport"
162 + " while another one is currently in progress.");
Nandana Duttcfb3d482019-02-20 11:25:35 +0000163 reportError(listener,
164 IDumpstateListener.BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS);
Nandana Dutt551906c2019-01-23 09:51:49 +0000165 return;
166 }
167
168 IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
169 if (ds == null) {
170 Slog.w(TAG, "Unable to get bugreport service");
171 reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
172 return;
173 }
174 try {
175 ds.startBugreport(callingUid, callingPackage,
176 bugreportFd, screenshotFd, bugreportMode, listener);
177 } catch (RemoteException e) {
178 reportError(listener, IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR);
179 }
180 }
181
182 @GuardedBy("mLock")
183 private boolean isDumpstateBinderServiceRunningLocked() {
184 IDumpstate ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
185 return ds != null;
Nandana Dutt3386fb72018-12-12 17:26:57 +0000186 }
187
188 /*
189 * Start and get a handle to the native implementation of {@code IDumpstate} which does the
190 * actual bugreport generation.
191 *
192 * <p>Generating bugreports requires root privileges. To limit the footprint
193 * of the root access, the actual generation in Dumpstate binary is accessed as a
194 * oneshot service 'bugreport'.
Nandana Dutt551906c2019-01-23 09:51:49 +0000195 *
196 * <p>Note that starting the service is achieved through setting a system property, which is
197 * not thread-safe. So the lock here offers thread-safety only among callers of the API.
Nandana Dutt3386fb72018-12-12 17:26:57 +0000198 */
Nandana Dutt551906c2019-01-23 09:51:49 +0000199 @GuardedBy("mLock")
200 private IDumpstate startAndGetDumpstateBinderServiceLocked() {
Nandana Dutt3386fb72018-12-12 17:26:57 +0000201 // Start bugreport service.
Nandana Duttb2da22a2019-01-23 08:39:05 +0000202 SystemProperties.set("ctl.start", BUGREPORT_SERVICE);
Nandana Dutt3386fb72018-12-12 17:26:57 +0000203
204 IDumpstate ds = null;
205 boolean timedOut = false;
206 int totalTimeWaitedMillis = 0;
207 int seedWaitTimeMillis = 500;
208 while (!timedOut) {
209 // Note that the binder service on the native side is "dumpstate".
210 ds = IDumpstate.Stub.asInterface(ServiceManager.getService("dumpstate"));
211 if (ds != null) {
212 Slog.i(TAG, "Got bugreport service handle.");
213 break;
214 }
215 SystemClock.sleep(seedWaitTimeMillis);
216 Slog.i(TAG,
217 "Waiting to get dumpstate service handle (" + totalTimeWaitedMillis + "ms)");
218 totalTimeWaitedMillis += seedWaitTimeMillis;
219 seedWaitTimeMillis *= 2;
220 timedOut = totalTimeWaitedMillis > DEFAULT_BUGREPORT_SERVICE_TIMEOUT_MILLIS;
221 }
222 if (timedOut) {
223 Slog.w(TAG,
224 "Timed out waiting to get dumpstate service handle ("
225 + totalTimeWaitedMillis + "ms)");
226 }
227 return ds;
228 }
Nandana Dutt551906c2019-01-23 09:51:49 +0000229
230 private void reportError(IDumpstateListener listener, int errorCode) {
231 try {
232 listener.onError(errorCode);
233 } catch (RemoteException e) {
234 // Something went wrong in binder or app process. There's nothing to do here.
235 Slog.w(TAG, "onError() transaction threw RemoteException: " + e.getMessage());
236 }
237 }
238
239 private void logAndThrow(String message) {
240 Slog.w(TAG, message);
241 throw new IllegalArgumentException(message);
242 }
Nandana Dutt3386fb72018-12-12 17:26:57 +0000243}