1 /* 2 * Copyright 2020 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 package com.android.server.tracing; 17 18 import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT; 19 import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN; 20 import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT; 21 import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR; 22 import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF; 23 import static com.android.internal.util.FrameworkStatsLog.TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING; 24 25 import android.Manifest; 26 import android.annotation.NonNull; 27 import android.content.ComponentName; 28 import android.content.Context; 29 import android.content.Intent; 30 import android.content.pm.PackageInfo; 31 import android.content.pm.PackageManager; 32 import android.content.pm.PackageManager.NameNotFoundException; 33 import android.content.pm.ServiceInfo; 34 import android.os.Binder; 35 import android.os.IMessenger; 36 import android.os.Message; 37 import android.os.ParcelFileDescriptor; 38 import android.os.ParcelFileDescriptor.AutoCloseInputStream; 39 import android.os.ParcelFileDescriptor.AutoCloseOutputStream; 40 import android.os.UserHandle; 41 import android.service.tracing.TraceReportService; 42 import android.tracing.ITracingServiceProxy; 43 import android.tracing.TraceReportParams; 44 import android.util.Log; 45 import android.util.LruCache; 46 import android.util.Slog; 47 48 import com.android.internal.infra.ServiceConnector; 49 import com.android.internal.util.FrameworkStatsLog; 50 import com.android.server.SystemService; 51 52 import java.io.IOException; 53 54 /** 55 * TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the 56 * other components (e.g. system tracing app Traceur, trace reporting apps). 57 * 58 * Access to this service is restricted via SELinux. Normal apps do not have access. 59 * 60 * @hide 61 */ 62 public class TracingServiceProxy extends SystemService { 63 public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy"; 64 private static final String TAG = "TracingServiceProxy"; 65 private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur"; 66 private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService"; 67 68 private static final int MAX_CACHED_REPORTER_SERVICES = 8; 69 70 // The maximum size of the trace allowed if the option |usePipeForTesting| is set. 71 // Note: this size MUST be smaller than the buffer size of the pipe (i.e. what you can 72 // write to the pipe without blocking) to avoid system_server blocking on this. 73 // (on Linux, the minimum value is 4K i.e. 1 minimally sized page). 74 private static final int MAX_FILE_SIZE_BYTES_TO_PIPE = 1024; 75 76 // Keep this in sync with the definitions in TraceService 77 private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED = 78 "com.android.traceur.NOTIFY_SESSION_STOPPED"; 79 private static final String INTENT_ACTION_NOTIFY_SESSION_STOLEN = 80 "com.android.traceur.NOTIFY_SESSION_STOLEN"; 81 82 private static final int REPORT_BEGIN = 83 TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BEGIN; 84 private static final int REPORT_SVC_HANDOFF = 85 TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_HANDOFF; 86 private static final int REPORT_BIND_PERM_INCORRECT = 87 TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_BIND_PERM_INCORRECT; 88 private static final int REPORT_SVC_PERM_MISSING = 89 TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_PERM_MISSING; 90 private static final int REPORT_SVC_COMM_ERROR = 91 TRACING_SERVICE_REPORT_EVENT__EVENT__TRACING_SERVICE_REPORT_SVC_COMM_ERROR; 92 93 private final Context mContext; 94 private final PackageManager mPackageManager; 95 private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices; 96 private boolean mServicePublished = false; 97 98 private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() { 99 /** 100 * Notifies system tracing app that a tracing session has ended. If a session is repurposed 101 * for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but 102 * there is no buffer available to dump. 103 */ 104 @Override 105 public void notifyTraceSessionEnded(boolean sessionStolen) { 106 TracingServiceProxy.this.notifyTraceur(sessionStolen); 107 } 108 109 @Override 110 public void reportTrace(@NonNull TraceReportParams params) { 111 TracingServiceProxy.this.reportTrace(params); 112 } 113 }; 114 TracingServiceProxy(Context context)115 public TracingServiceProxy(Context context) { 116 super(context); 117 mContext = context; 118 mPackageManager = context.getPackageManager(); 119 mCachedReporterServices = new LruCache<>(MAX_CACHED_REPORTER_SERVICES); 120 } 121 122 @Override onStart()123 public void onStart() {} 124 125 @Override onUserUnlocking(@onNull TargetUser user)126 public void onUserUnlocking(@NonNull TargetUser user) { 127 // We need the device storage to be unlocked before we can accept and forward 128 // requests. 129 if (!mServicePublished) { 130 publishBinderService(TRACING_SERVICE_PROXY_BINDER_NAME, mTracingServiceProxy); 131 mServicePublished = true; 132 } 133 } 134 notifyTraceur(boolean sessionStolen)135 private void notifyTraceur(boolean sessionStolen) { 136 final Intent intent = new Intent(); 137 138 try { 139 // Validate that Traceur is a system app. 140 PackageInfo info = mPackageManager.getPackageInfo(TRACING_APP_PACKAGE_NAME, 141 PackageManager.MATCH_SYSTEM_ONLY); 142 143 intent.setClassName(info.packageName, TRACING_APP_ACTIVITY); 144 if (sessionStolen) { 145 intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOLEN); 146 } else { 147 intent.setAction(INTENT_ACTION_NOTIFY_SESSION_STOPPED); 148 } 149 150 final long identity = Binder.clearCallingIdentity(); 151 try { 152 mContext.startForegroundServiceAsUser(intent, UserHandle.SYSTEM); 153 } catch (RuntimeException e) { 154 Log.e(TAG, "Failed to notifyTraceSessionEnded", e); 155 } finally { 156 Binder.restoreCallingIdentity(identity); 157 } 158 159 } catch (NameNotFoundException e) { 160 Log.e(TAG, "Failed to locate Traceur", e); 161 } 162 } 163 reportTrace(@onNull TraceReportParams params)164 private void reportTrace(@NonNull TraceReportParams params) { 165 FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BEGIN, 166 params.uuidLsb, params.uuidMsb); 167 168 // We don't need to do any permission checks on the caller because access 169 // to this service is guarded by SELinux. 170 ComponentName component = new ComponentName(params.reporterPackageName, 171 params.reporterClassName); 172 if (!hasBindServicePermission(component)) { 173 FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_BIND_PERM_INCORRECT, 174 params.uuidLsb, params.uuidMsb); 175 return; 176 } 177 boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP); 178 boolean hasUsageStatsPermission = hasPermission(component, 179 Manifest.permission.PACKAGE_USAGE_STATS); 180 if (!hasDumpPermission || !hasUsageStatsPermission) { 181 FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_PERM_MISSING, 182 params.uuidLsb, params.uuidMsb); 183 return; 184 } 185 final long ident = Binder.clearCallingIdentity(); 186 try { 187 reportTrace(getOrCreateReporterService(component), params); 188 } finally { 189 Binder.restoreCallingIdentity(ident); 190 } 191 } 192 reportTrace( @onNull ServiceConnector<IMessenger> reporterService, @NonNull TraceReportParams params)193 private void reportTrace( 194 @NonNull ServiceConnector<IMessenger> reporterService, 195 @NonNull TraceReportParams params) { 196 reporterService.post(messenger -> { 197 if (params.usePipeForTesting) { 198 ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe(); 199 try (AutoCloseInputStream i = new AutoCloseInputStream(params.fd)) { 200 try (AutoCloseOutputStream o = new AutoCloseOutputStream(pipe[1])) { 201 byte[] array = i.readNBytes(MAX_FILE_SIZE_BYTES_TO_PIPE); 202 if (array.length == MAX_FILE_SIZE_BYTES_TO_PIPE) { 203 throw new IllegalArgumentException( 204 "Trace file too large when |usePipeForTesting| is set."); 205 } 206 o.write(array); 207 } 208 } 209 params.fd = pipe[0]; 210 } 211 212 Message message = Message.obtain(); 213 message.what = TraceReportService.MSG_REPORT_TRACE; 214 message.obj = params; 215 messenger.send(message); 216 217 FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_HANDOFF, 218 params.uuidLsb, params.uuidMsb); 219 }).whenComplete((res, err) -> { 220 if (err != null) { 221 FrameworkStatsLog.write(TRACING_SERVICE_REPORT_EVENT, REPORT_SVC_COMM_ERROR, 222 params.uuidLsb, params.uuidMsb); 223 Slog.e(TAG, "Failed to report trace", err); 224 } 225 try { 226 params.fd.close(); 227 } catch (IOException ignored) { 228 } 229 }); 230 } 231 getOrCreateReporterService( @onNull ComponentName component)232 private ServiceConnector<IMessenger> getOrCreateReporterService( 233 @NonNull ComponentName component) { 234 ServiceConnector<IMessenger> connector = mCachedReporterServices.get(component); 235 if (connector == null) { 236 Intent intent = new Intent(); 237 intent.setComponent(component); 238 connector = new ServiceConnector.Impl<IMessenger>( 239 mContext, intent, 240 Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY, 241 mContext.getUser().getIdentifier(), IMessenger.Stub::asInterface) { 242 private static final long DISCONNECT_TIMEOUT_MS = 15_000; 243 private static final long REQUEST_TIMEOUT_MS = 10_000; 244 245 @Override 246 protected long getAutoDisconnectTimeoutMs() { 247 return DISCONNECT_TIMEOUT_MS; 248 } 249 250 @Override 251 protected long getRequestTimeoutMs() { 252 return REQUEST_TIMEOUT_MS; 253 } 254 }; 255 mCachedReporterServices.put(intent.getComponent(), connector); 256 } 257 return connector; 258 } 259 hasPermission(@onNull ComponentName componentName, @NonNull String permission)260 private boolean hasPermission(@NonNull ComponentName componentName, 261 @NonNull String permission) throws SecurityException { 262 if (mPackageManager.checkPermission(permission, componentName.getPackageName()) 263 != PackageManager.PERMISSION_GRANTED) { 264 Slog.e(TAG, 265 "Trace reporting service " + componentName.toShortString() + " does not have " 266 + permission + " permission"); 267 return false; 268 } 269 return true; 270 } 271 hasBindServicePermission(@onNull ComponentName componentName)272 private boolean hasBindServicePermission(@NonNull ComponentName componentName) { 273 ServiceInfo info; 274 try { 275 info = mPackageManager.getServiceInfo(componentName, 0); 276 } catch (NameNotFoundException ex) { 277 Slog.e(TAG, 278 "Trace reporting service " + componentName.toShortString() + " does not exist"); 279 return false; 280 } 281 if (!Manifest.permission.BIND_TRACE_REPORT_SERVICE.equals(info.permission)) { 282 Slog.e(TAG, 283 "Trace reporting service " + componentName.toShortString() 284 + " does not request " + Manifest.permission.BIND_TRACE_REPORT_SERVICE 285 + " permission; instead requests " + info.permission); 286 return false; 287 } 288 return true; 289 } 290 } 291