Merge "base: add support for routing traces to reporters"

This commit is contained in:
Lalit Maganti 2022-01-28 12:15:57 +00:00 committed by Gerrit Code Review
commit 3a24d6bffb
8 changed files with 417 additions and 11 deletions

View File

@ -63,6 +63,7 @@ package android {
field public static final String BIND_TELEPHONY_NETWORK_SERVICE = "android.permission.BIND_TELEPHONY_NETWORK_SERVICE";
field public static final String BIND_TEXTCLASSIFIER_SERVICE = "android.permission.BIND_TEXTCLASSIFIER_SERVICE";
field public static final String BIND_TIME_ZONE_PROVIDER_SERVICE = "android.permission.BIND_TIME_ZONE_PROVIDER_SERVICE";
field public static final String BIND_TRACE_REPORT_SERVICE = "android.permission.BIND_TRACE_REPORT_SERVICE";
field public static final String BIND_TRANSLATION_SERVICE = "android.permission.BIND_TRANSLATION_SERVICE";
field public static final String BIND_TRUST_AGENT = "android.permission.BIND_TRUST_AGENT";
field public static final String BIND_TV_REMOTE_SERVICE = "android.permission.BIND_TV_REMOTE_SERVICE";
@ -10120,6 +10121,22 @@ package android.service.timezone {
}
package android.service.tracing {
public class TraceReportService extends android.app.Service {
ctor public TraceReportService();
method @Nullable public android.os.IBinder onBind(@NonNull android.content.Intent);
method public boolean onMessage(@NonNull android.os.Message);
method public void onReportTrace(@NonNull android.service.tracing.TraceReportService.TraceParams);
}
public static final class TraceReportService.TraceParams {
method @NonNull public android.os.ParcelFileDescriptor getFd();
method @NonNull public java.util.UUID getUuid();
}
}
package android.service.translation {
public abstract class TranslationService extends android.app.Service {

View File

@ -33,6 +33,11 @@ filegroup {
srcs: ["android/tracing/ITracingServiceProxy.aidl"],
}
filegroup {
name: "TraceReportParams.aidl",
srcs: ["android/tracing/TraceReportParams.aidl"],
}
// These are subset of framework-core-sources that are needed by the
// android.test.mock library. The implementation of android.test.mock references
// private members of various components to allow mocking of classes that cannot

View File

@ -0,0 +1,165 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.service.tracing;
import static android.annotation.SystemApi.Client.PRIVILEGED_APPS;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.SystemApi;
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.os.Message;
import android.os.Messenger;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.tracing.TraceReportParams;
import android.util.Log;
import java.io.IOException;
import java.util.UUID;
/**
* Service to be sub-classed and exposed by (privileged) apps which want to report
* system traces.
* <p>
* Subclasses should implement the onReportTrace method to handle traces reported
* to them.
* </p>
* <pre>
* public class SampleReportService extends TraceReportService {
* public void onReportTrace(TraceParams args) {
* // --- Implementation goes here ---
* }
* }
* </pre>
* <p>
* The service declaration in the application manifest must specify
* BIND_TRACE_REPORT_SERVICE in the permission attribute.
* </p>
* <pre>
* &lt;application>
* &lt;service android:name=".SampleReportService"
* android:permission="android.permission.BIND_TRACE_REPORT_SERVICE">
* &lt;/service>
* &lt;/application>
* </pre>
*
* Moreover, the package containing this service must hold the DUMP and PACKAGE_USAGE_STATS
* permissions.
*
* @hide
*/
@SystemApi(client = PRIVILEGED_APPS)
public class TraceReportService extends Service {
private static final String TAG = "TraceReportService";
private Messenger mMessenger = null;
/**
* Public to allow this to be used by TracingServiceProxy in system_server.
*
* @hide
*/
public static final int MSG_REPORT_TRACE = 1;
/**
* Contains information about the trace which is being reported.
*
* @hide
*/
@SystemApi(client = PRIVILEGED_APPS)
public static final class TraceParams {
private final ParcelFileDescriptor mFd;
private final UUID mUuid;
private TraceParams(TraceReportParams params) {
mFd = params.fd;
mUuid = new UUID(params.uuidMsb, params.uuidLsb);
}
/**
* Returns the ParcelFileDescriptor for the collected trace.
*/
@NonNull
public ParcelFileDescriptor getFd() {
return mFd;
}
/**
* Returns the UUID of the trace; this is exactly the UUID created by the tracing system
* (i.e. Perfetto) and is also present inside the trace file.
*/
@NonNull
public UUID getUuid() {
return mUuid;
}
}
// Methods to override.
/**
* Called when a trace is reported and sent to this class.
*
* Note: the trace file descriptor should not be persisted beyond the lifetime of this
* function as it is owned by the framework and will be closed immediately after this function
* returns: if future use of the fd is needed, it should be duped.
*/
public void onReportTrace(@NonNull TraceParams args) {
}
// Optional methods to override.
// Realistically, these methods are internal implementation details but since this class is
// a SystemApi, it's better to err on the side of flexibility just in-case we need to override
// these methods down the line.
/**
* Handles binder calls from system_server.
*/
public boolean onMessage(@NonNull Message msg) {
if (msg.what == MSG_REPORT_TRACE) {
if (!(msg.obj instanceof TraceReportParams)) {
Log.e(TAG, "Received invalid type for report trace message.");
return false;
}
TraceParams params = new TraceParams((TraceReportParams) msg.obj);
try {
onReportTrace(params);
} finally {
try {
params.getFd().close();
} catch (IOException ignored) {
}
}
return true;
}
return false;
}
/**
* Returns an IBinder for handling binder calls from system_server.
*/
@Nullable
@Override
public IBinder onBind(@NonNull Intent intent) {
if (mMessenger == null) {
mMessenger = new Messenger(new Handler(Looper.getMainLooper(), this::onMessage));
}
return mMessenger.getBinder();
}
}

View File

@ -16,17 +16,25 @@
package android.tracing;
import android.tracing.TraceReportParams;
/**
* Binder interface for the TracingServiceProxy running in system_server.
*
* {@hide}
*/
interface ITracingServiceProxy
{
interface ITracingServiceProxy {
/**
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
* there is no buffer available to dump.
*/
oneway void notifyTraceSessionEnded(boolean sessionStolen);
/**
* Notifies the specified service that a trace has been captured. The contents of |params|
* contains the intended recipient (package and class) of this trace as well as a file
* descriptor to an unlinked trace |fd| (i.e. an fd opened using O_TMPFILE).
*/
oneway void reportTrace(in TraceReportParams params);
}

View File

@ -0,0 +1,59 @@
/**
* Copyright (c) 2021, The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package android.tracing;
import android.os.ParcelFileDescriptor;
/*
* Parameters for a trace report.
*
* See ITracingServiceProxy::reportTrace for more details.
*
* @hide
*/
parcelable TraceReportParams {
// The package name containing the reporter service (see |reporterClassName|).
String reporterPackageName;
// The class name of the reporter service. The framework will bind to this service and pass the
// trace fd and metadata to this class.
// This class should be "trusted" (in practice this means being a priv_app + having DUMP and
// USAGE_STATS permissions).
String reporterClassName;
// The file descriptor for the trace file. This will be an unlinked file fd (i.e. created
// with O_TMPFILE); the intention is that reporter classes link this fd into a app-private
// folder for reporting when conditions are right (e.g. charging, on unmetered networks etc).
ParcelFileDescriptor fd;
// The least-significant-bytes of the UUID of this trace.
long uuidLsb;
// The most-significant-bytes of the UUID of this trace.
long uuidMsb;
// Flag indicating whether, instead of passing the fd from the trace collector, to pass a
// pipe fd from system_server and send the file over it.
//
// This flag is necessary because there is no good way to write a CTS test where a helper
// priv_app (in terms of SELinux) is needed (this is because priv_apps are supposed to be
// preinstalled on the system partition). By creating a pipe in system_server we work around
// this restriction. Note that there is a maximum allowed file size if this flag is set
// (see TracingServiceProxy). Further note that, even though SELinux may be worked around,
// manifest (i.e. framework) permissions are still checked even if this flag is set.
boolean usePipeForTesting;
}

View File

@ -3453,6 +3453,13 @@
<permission android:name="android.permission.SIGNAL_PERSISTENT_PROCESSES"
android:protectionLevel="signature|privileged|development" />
<!-- @hide @SystemApi Must be required by a
{@link com.android.service.tracing.TraceReportService}, to ensure that only the system
can bind to it.
<p>Not for use by third-party applications. -->
<permission android:name="android.permission.BIND_TRACE_REPORT_SERVICE"
android:protectionLevel="signature" />
<!-- @hide @SystemApi @TestApi
Allow an application to approve incident and bug reports to be
shared off-device. There can be only one application installed on the

View File

@ -37,6 +37,7 @@ cc_library_shared {
srcs: [
":ITracingServiceProxy.aidl",
":TraceReportParams.aidl",
],
shared_libs: [

View File

@ -15,34 +15,56 @@
*/
package com.android.server.tracing;
import android.Manifest;
import android.annotation.NonNull;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.ServiceInfo;
import android.os.Binder;
import android.os.IMessenger;
import android.os.Message;
import android.os.ParcelFileDescriptor;
import android.os.ParcelFileDescriptor.AutoCloseInputStream;
import android.os.ParcelFileDescriptor.AutoCloseOutputStream;
import android.os.UserHandle;
import android.service.tracing.TraceReportService;
import android.tracing.ITracingServiceProxy;
import android.tracing.TraceReportParams;
import android.util.Log;
import android.util.LruCache;
import android.util.Slog;
import com.android.internal.infra.ServiceConnector;
import com.android.server.SystemService;
import java.io.IOException;
/**
* TracingServiceProxy is the system_server intermediary between the Perfetto tracing daemon and the
* system tracing app Traceur.
* other components (e.g. system tracing app Traceur, trace reporting apps).
*
* Access to this service is restricted via SELinux. Normal apps do not have access.
*
* @hide
*/
public class TracingServiceProxy extends SystemService {
private static final String TAG = "TracingServiceProxy";
public static final String TRACING_SERVICE_PROXY_BINDER_NAME = "tracing.proxy";
private static final String TAG = "TracingServiceProxy";
private static final String TRACING_APP_PACKAGE_NAME = "com.android.traceur";
private static final String TRACING_APP_ACTIVITY = "com.android.traceur.StopTraceService";
private static final int MAX_CACHED_REPORTER_SERVICES = 8;
// The maximum size of the trace allowed if the option |usePipeForTesting| is set.
// Note: this size MUST be smaller than the buffer size of the pipe (i.e. what you can
// write to the pipe without blocking) to avoid system_server blocking on this.
// (on Linux, the minimum value is 4K i.e. 1 minimally sized page).
private static final int MAX_FILE_SIZE_BYTES_TO_PIPE = 1024;
// Keep this in sync with the definitions in TraceService
private static final String INTENT_ACTION_NOTIFY_SESSION_STOPPED =
"com.android.traceur.NOTIFY_SESSION_STOPPED";
@ -51,16 +73,22 @@ public class TracingServiceProxy extends SystemService {
private final Context mContext;
private final PackageManager mPackageManager;
private final LruCache<ComponentName, ServiceConnector<IMessenger>> mCachedReporterServices;
private final ITracingServiceProxy.Stub mTracingServiceProxy = new ITracingServiceProxy.Stub() {
/**
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
* there is no buffer available to dump.
*/
* Notifies system tracing app that a tracing session has ended. If a session is repurposed
* for use in a bugreport, sessionStolen can be set to indicate that tracing has ended but
* there is no buffer available to dump.
*/
@Override
public void notifyTraceSessionEnded(boolean sessionStolen) {
notifyTraceur(sessionStolen);
TracingServiceProxy.this.notifyTraceur(sessionStolen);
}
@Override
public void reportTrace(@NonNull TraceReportParams params) {
TracingServiceProxy.this.reportTrace(params);
}
};
@ -68,6 +96,7 @@ public class TracingServiceProxy extends SystemService {
super(context);
mContext = context;
mPackageManager = context.getPackageManager();
mCachedReporterServices = new LruCache<>(MAX_CACHED_REPORTER_SERVICES);
}
@Override
@ -103,4 +132,119 @@ public class TracingServiceProxy extends SystemService {
Log.e(TAG, "Failed to locate Traceur", e);
}
}
private void reportTrace(@NonNull TraceReportParams params) {
// We don't need to do any permission checks on the caller because access
// to this service is guarded by SELinux.
ComponentName component = new ComponentName(params.reporterPackageName,
params.reporterClassName);
if (!hasBindServicePermission(component)) {
return;
}
boolean hasDumpPermission = hasPermission(component, Manifest.permission.DUMP);
boolean hasUsageStatsPermission = hasPermission(component,
Manifest.permission.PACKAGE_USAGE_STATS);
if (!hasDumpPermission || !hasUsageStatsPermission) {
return;
}
final long ident = Binder.clearCallingIdentity();
try {
reportTrace(getOrCreateReporterService(component), params);
} finally {
Binder.restoreCallingIdentity(ident);
}
}
private void reportTrace(
@NonNull ServiceConnector<IMessenger> reporterService,
@NonNull TraceReportParams params) {
reporterService.post(messenger -> {
if (params.usePipeForTesting) {
ParcelFileDescriptor[] pipe = ParcelFileDescriptor.createPipe();
try (AutoCloseInputStream i = new AutoCloseInputStream(params.fd)) {
try (AutoCloseOutputStream o = new AutoCloseOutputStream(pipe[1])) {
byte[] array = i.readNBytes(MAX_FILE_SIZE_BYTES_TO_PIPE);
if (array.length == MAX_FILE_SIZE_BYTES_TO_PIPE) {
throw new IllegalArgumentException(
"Trace file too large when |usePipeForTesting| is set.");
}
o.write(array);
}
}
params.fd = pipe[0];
}
Message message = Message.obtain();
message.what = TraceReportService.MSG_REPORT_TRACE;
message.obj = params;
messenger.send(message);
}).whenComplete((res, err) -> {
if (err != null) {
Slog.e(TAG, "Failed to report trace", err);
}
try {
params.fd.close();
} catch (IOException ignored) {
}
});
}
private ServiceConnector<IMessenger> getOrCreateReporterService(
@NonNull ComponentName component) {
ServiceConnector<IMessenger> connector = mCachedReporterServices.get(component);
if (connector == null) {
Intent intent = new Intent();
intent.setComponent(component);
connector = new ServiceConnector.Impl<IMessenger>(
mContext, intent,
Context.BIND_AUTO_CREATE | Context.BIND_WAIVE_PRIORITY,
mContext.getUser().getIdentifier(), IMessenger.Stub::asInterface) {
private static final long DISCONNECT_TIMEOUT_MS = 15_000;
private static final long REQUEST_TIMEOUT_MS = 10_000;
@Override
protected long getAutoDisconnectTimeoutMs() {
return DISCONNECT_TIMEOUT_MS;
}
@Override
protected long getRequestTimeoutMs() {
return REQUEST_TIMEOUT_MS;
}
};
mCachedReporterServices.put(intent.getComponent(), connector);
}
return connector;
}
private boolean hasPermission(@NonNull ComponentName componentName,
@NonNull String permission) throws SecurityException {
if (mPackageManager.checkPermission(permission, componentName.getPackageName())
!= PackageManager.PERMISSION_GRANTED) {
Slog.e(TAG,
"Trace reporting service " + componentName.toShortString() + " does not have "
+ permission + " permission");
return false;
}
return true;
}
private boolean hasBindServicePermission(@NonNull ComponentName componentName) {
ServiceInfo info;
try {
info = mPackageManager.getServiceInfo(componentName, 0);
} catch (NameNotFoundException ex) {
Slog.e(TAG,
"Trace reporting service " + componentName.toShortString() + " does not exist");
return false;
}
if (!Manifest.permission.BIND_TRACE_REPORT_SERVICE.equals(info.permission)) {
Slog.e(TAG,
"Trace reporting service " + componentName.toShortString()
+ " does not request " + Manifest.permission.BIND_TRACE_REPORT_SERVICE
+ " permission; instead requests " + info.permission);
return false;
}
return true;
}
}