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