Merge "Make BugreportManager a public API that respects carrier privileges." am: 0dcf4de7df

Original change: https://android-review.googlesource.com/c/platform/frameworks/base/+/1552883

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I9e835752e37bcb99e35078025bb43fb46313833f
This commit is contained in:
Treehugger Robot
2021-01-19 00:57:08 +00:00
committed by Automerger Merge Worker
5 changed files with 170 additions and 96 deletions

View File

@@ -10187,6 +10187,7 @@ package android.content {
field public static final String BIOMETRIC_SERVICE = "biometric";
field public static final String BLOB_STORE_SERVICE = "blob_store";
field public static final String BLUETOOTH_SERVICE = "bluetooth";
field public static final String BUGREPORT_SERVICE = "bugreport";
field public static final String CAMERA_SERVICE = "camera";
field public static final String CAPTIONING_SERVICE = "captioning";
field public static final String CARRIER_CONFIG_SERVICE = "carrier_config";
@@ -29596,6 +29597,24 @@ package android.os {
method public boolean unlinkToDeath(@NonNull android.os.IBinder.DeathRecipient, int);
}
public final class BugreportManager {
method public void cancelBugreport();
method public void startConnectivityBugreport(@NonNull android.os.ParcelFileDescriptor, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
}
public abstract static class BugreportManager.BugreportCallback {
ctor public BugreportManager.BugreportCallback();
method public void onEarlyReportFinished();
method public void onError(int);
method public void onFinished();
method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float);
field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5
field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1
field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2
field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4
field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3
}
public class Build {
ctor public Build();
method @NonNull public static java.util.List<android.os.Build.Partition> getFingerprintedPartitions();

View File

@@ -1684,7 +1684,6 @@ package android.content {
field public static final String APP_PREDICTION_SERVICE = "app_prediction";
field public static final String BACKUP_SERVICE = "backup";
field public static final String BATTERY_STATS_SERVICE = "batterystats";
field public static final String BUGREPORT_SERVICE = "bugreport";
field public static final String CONTENT_SUGGESTIONS_SERVICE = "content_suggestions";
field public static final String CONTEXTHUB_SERVICE = "contexthub";
field public static final String ETHERNET_SERVICE = "ethernet";
@@ -7046,24 +7045,10 @@ package android.os {
}
public final class BugreportManager {
method @RequiresPermission(android.Manifest.permission.DUMP) public void cancelBugreport();
method @RequiresPermission(android.Manifest.permission.DUMP) public void requestBugreport(@NonNull android.os.BugreportParams, @Nullable CharSequence, @Nullable CharSequence);
method @RequiresPermission(android.Manifest.permission.DUMP) public void startBugreport(@NonNull android.os.ParcelFileDescriptor, @Nullable android.os.ParcelFileDescriptor, @NonNull android.os.BugreportParams, @NonNull java.util.concurrent.Executor, @NonNull android.os.BugreportManager.BugreportCallback);
}
public abstract static class BugreportManager.BugreportCallback {
ctor public BugreportManager.BugreportCallback();
method public void onEarlyReportFinished();
method public void onError(int);
method public void onFinished();
method public void onProgress(@FloatRange(from=0.0f, to=100.0f) float);
field public static final int BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS = 5; // 0x5
field public static final int BUGREPORT_ERROR_INVALID_INPUT = 1; // 0x1
field public static final int BUGREPORT_ERROR_RUNTIME = 2; // 0x2
field public static final int BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT = 4; // 0x4
field public static final int BUGREPORT_ERROR_USER_DENIED_CONSENT = 3; // 0x3
}
public final class BugreportParams {
ctor public BugreportParams(int);
method public int getMode();

View File

@@ -4999,9 +4999,7 @@ public abstract class Context {
* Service to capture a bugreport.
* @see #getSystemService(String)
* @see android.os.BugreportManager
* @hide
*/
@SystemApi
public static final String BUGREPORT_SERVICE = "bugreport";
/**

View File

@@ -22,6 +22,7 @@ import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.app.ActivityManager;
@@ -40,12 +41,7 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.concurrent.Executor;
/**
* Class that provides a privileged API to capture and consume bugreports.
*
* @hide
*/
@SystemApi
/** Class that provides a privileged API to capture and consume bugreports. */
@SystemService(Context.BUGREPORT_SERVICE)
public final class BugreportManager {
@@ -60,28 +56,30 @@ public final class BugreportManager {
mBinder = binder;
}
/**
* An interface describing the callback for bugreport progress and status.
*/
/** An interface describing the callback for bugreport progress and status. */
public abstract static class BugreportCallback {
/** @hide */
/**
* Possible error codes taking a bugreport can encounter.
*
* @hide
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
BUGREPORT_ERROR_INVALID_INPUT,
BUGREPORT_ERROR_RUNTIME,
BUGREPORT_ERROR_USER_DENIED_CONSENT,
BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
})
/** Possible error codes taking a bugreport can encounter */
@IntDef(
prefix = {"BUGREPORT_ERROR_"},
value = {
BUGREPORT_ERROR_INVALID_INPUT,
BUGREPORT_ERROR_RUNTIME,
BUGREPORT_ERROR_USER_DENIED_CONSENT,
BUGREPORT_ERROR_USER_CONSENT_TIMED_OUT,
BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS
})
public @interface BugreportErrorCode {}
/** The input options were invalid */
public static final int BUGREPORT_ERROR_INVALID_INPUT =
IDumpstateListener.BUGREPORT_ERROR_INVALID_INPUT;
/** A runtime error occured */
/** A runtime error occurred */
public static final int BUGREPORT_ERROR_RUNTIME =
IDumpstateListener.BUGREPORT_ERROR_RUNTIME_ERROR;
@@ -99,6 +97,7 @@ public final class BugreportManager {
/**
* Called when there is a progress update.
*
* @param progress the progress in [0.0, 100.0]
*/
public void onProgress(@FloatRange(from = 0f, to = 100f) float progress) {}
@@ -113,14 +112,12 @@ public final class BugreportManager {
* out, but the bugreport could be available in the internal directory of dumpstate for
* manual retrieval.
*
* <p> If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the
* caller should try later, as only one bugreport can be in progress at a time.
* <p>If {@code BUGREPORT_ERROR_ANOTHER_REPORT_IN_PROGRESS} is passed, then the caller
* should try later, as only one bugreport can be in progress at a time.
*/
public void onError(@BugreportErrorCode int errorCode) {}
/**
* Called when taking bugreport finishes successfully.
*/
/** Called when taking bugreport finishes successfully. */
public void onFinished() {}
/**
@@ -137,20 +134,23 @@ public final class BugreportManager {
* seconds to return in the worst case. {@code callback} will receive progress and status
* updates.
*
* <p>The bugreport artifacts will be copied over to the given file descriptors only if the
* user consents to sharing with the calling app.
* <p>The bugreport artifacts will be copied over to the given file descriptors only if the user
* consents to sharing with the calling app.
*
* <p>{@link BugreportManager} takes ownership of {@code bugreportFd} and {@code screenshotFd}.
*
* @param bugreportFd file to write the bugreport. This should be opened in write-only,
* append mode.
* @param screenshotFd file to write the screenshot, if necessary. This should be opened
* in write-only, append mode.
* @param bugreportFd file to write the bugreport. This should be opened in write-only, append
* mode.
* @param screenshotFd file to write the screenshot, if necessary. This should be opened in
* write-only, append mode.
* @param params options that specify what kind of a bugreport should be taken
* @param callback callback for progress and status updates
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.DUMP)
public void startBugreport(@NonNull ParcelFileDescriptor bugreportFd,
public void startBugreport(
@NonNull ParcelFileDescriptor bugreportFd,
@Nullable ParcelFileDescriptor screenshotFd,
@NonNull BugreportParams params,
@NonNull @CallbackExecutor Executor executor,
@@ -164,17 +164,21 @@ public final class BugreportManager {
boolean isScreenshotRequested = screenshotFd != null;
if (screenshotFd == null) {
// Binder needs a valid File Descriptor to be passed
screenshotFd = ParcelFileDescriptor.open(new File("/dev/null"),
ParcelFileDescriptor.MODE_READ_ONLY);
screenshotFd =
ParcelFileDescriptor.open(
new File("/dev/null"), ParcelFileDescriptor.MODE_READ_ONLY);
}
DumpstateListener dsListener = new DumpstateListener(executor, callback,
isScreenshotRequested);
DumpstateListener dsListener =
new DumpstateListener(executor, callback, isScreenshotRequested);
// Note: mBinder can get callingUid from the binder transaction.
mBinder.startBugreport(-1 /* callingUid */,
mBinder.startBugreport(
-1 /* callingUid */,
mContext.getOpPackageName(),
bugreportFd.getFileDescriptor(),
screenshotFd.getFileDescriptor(),
params.getMode(), dsListener, isScreenshotRequested);
params.getMode(),
dsListener,
isScreenshotRequested);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
} catch (FileNotFoundException e) {
@@ -188,15 +192,61 @@ public final class BugreportManager {
}
}
/**
* Starts a connectivity bugreport.
*
* <p>The connectivity bugreport is a specialized version of bugreport that only includes
* information specifically for debugging connectivity-related issues (e.g. telephony, wi-fi,
* and IP networking issues). It is intended primarily for use by OEMs and network providers
* such as mobile network operators. In addition to generally excluding information that isn't
* targeted to connectivity debugging, this type of bugreport excludes PII and sensitive
* information that isn't strictly necessary for connectivity debugging.
*
* <p>The calling app MUST have a context-specific reason for requesting a connectivity
* bugreport, such as detecting a connectivity-related issue. This API SHALL NOT be used to
* perform random sampling from a fleet of public end-user devices.
*
* <p>Calling this API will cause the system to ask the user for consent every single time. The
* bugreport artifacts will be copied over to the given file descriptors only if the user
* consents to sharing with the calling app.
*
* <p>This starts a bugreport in the background. However the call itself can take several
* seconds to return in the worst case. {@code callback} will receive progress and status
* updates.
*
* <p>Requires that the calling app has carrier privileges (see {@link
* android.telephony.TelephonyManager#hasCarrierPrivileges}) on any active subscription.
*
* @param bugreportFd file to write the bugreport. This should be opened in write-only, append
* mode.
* @param callback callback for progress and status updates.
*/
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
public void startConnectivityBugreport(
@NonNull ParcelFileDescriptor bugreportFd,
@NonNull @CallbackExecutor Executor executor,
@NonNull BugreportCallback callback) {
startBugreport(
bugreportFd,
null /* screenshotFd */,
new BugreportParams(BugreportParams.BUGREPORT_MODE_TELEPHONY),
executor,
callback);
}
/**
* Cancels the currently running bugreport.
*
* <p>Apps are only able to cancel their own bugreports. App A cannot cancel a bugreport started
* by app B.
*
* <p>Requires permission: {@link android.Manifest.permission#DUMP} or that the calling app has
* carrier privileges (see {@link android.telephony.TelephonyManager#hasCarrierPrivileges}) on
* any active subscription.
*
* @throws SecurityException if trying to cancel another app's bugreport in progress
*/
@RequiresPermission(android.Manifest.permission.DUMP)
@SuppressAutoDoc // Blocked by b/72967236 - no support for carrier privileges
public void cancelBugreport() {
try {
mBinder.cancelBugreport(-1 /* callingUid */, mContext.getOpPackageName());
@@ -209,23 +259,26 @@ public final class BugreportManager {
* Requests a bugreport.
*
* <p>This requests the platform/system to take a bugreport and makes the final bugreport
* available to the user. The user may choose to share it with another app, but the bugreport
* is never given back directly to the app that requested it.
* available to the user. The user may choose to share it with another app, but the bugreport is
* never given back directly to the app that requested it.
*
* @param params {@link BugreportParams} that specify what kind of a bugreport should
* be taken, please note that not all kinds of bugreport allow for a
* progress notification
* @param shareTitle title on the final share notification
* @param params {@link BugreportParams} that specify what kind of a bugreport should be taken,
* please note that not all kinds of bugreport allow for a progress notification
* @param shareTitle title on the final share notification
* @param shareDescription description on the final share notification
* @hide
*/
@SystemApi
@RequiresPermission(android.Manifest.permission.DUMP)
public void requestBugreport(@NonNull BugreportParams params, @Nullable CharSequence shareTitle,
public void requestBugreport(
@NonNull BugreportParams params,
@Nullable CharSequence shareTitle,
@Nullable CharSequence shareDescription) {
try {
String title = shareTitle == null ? null : shareTitle.toString();
String description = shareDescription == null ? null : shareDescription.toString();
ActivityManager.getService().requestBugReportWithDescription(title, description,
params.getMode());
ActivityManager.getService()
.requestBugReportWithDescription(title, description, params.getMode());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
@@ -236,8 +289,8 @@ public final class BugreportManager {
private final BugreportCallback mCallback;
private final boolean mIsScreenshotRequested;
DumpstateListener(Executor executor, BugreportCallback callback,
boolean isScreenshotRequested) {
DumpstateListener(
Executor executor, BugreportCallback callback, boolean isScreenshotRequested) {
mExecutor = executor;
mCallback = callback;
mIsScreenshotRequested = isScreenshotRequested;
@@ -247,9 +300,7 @@ public final class BugreportManager {
public void onProgress(int progress) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> {
mCallback.onProgress(progress);
});
mExecutor.execute(() -> mCallback.onProgress(progress));
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -259,9 +310,7 @@ public final class BugreportManager {
public void onError(int errorCode) throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> {
mCallback.onError(errorCode);
});
mExecutor.execute(() -> mCallback.onError(errorCode));
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -271,9 +320,7 @@ public final class BugreportManager {
public void onFinished() throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> {
mCallback.onFinished();
});
mExecutor.execute(() -> mCallback.onFinished());
} finally {
Binder.restoreCallingIdentity(identity);
}
@@ -288,20 +335,19 @@ public final class BugreportManager {
Handler mainThreadHandler = new Handler(Looper.getMainLooper());
mainThreadHandler.post(
() -> {
int message = success ? R.string.bugreport_screenshot_success_toast
: R.string.bugreport_screenshot_failure_toast;
int message =
success
? R.string.bugreport_screenshot_success_toast
: R.string.bugreport_screenshot_failure_toast;
Toast.makeText(mContext, message, Toast.LENGTH_LONG).show();
});
}
@Override
public void onUiIntensiveBugreportDumpsFinished()
throws RemoteException {
public void onUiIntensiveBugreportDumpsFinished() throws RemoteException {
final long identity = Binder.clearCallingIdentity();
try {
mExecutor.execute(() -> {
mCallback.onEarlyReportFinished();
});
mExecutor.execute(() -> mCallback.onEarlyReportFinished());
} finally {
Binder.restoreCallingIdentity(identity);
}

View File

@@ -21,6 +21,7 @@ import android.annotation.RequiresPermission;
import android.app.ActivityManager;
import android.app.AppOpsManager;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.BugreportParams;
@@ -31,6 +32,7 @@ import android.os.ServiceManager;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.UserManager;
import android.telephony.TelephonyManager;
import android.util.ArraySet;
import android.util.Slog;
@@ -53,11 +55,13 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
private final Object mLock = new Object();
private final Context mContext;
private final AppOpsManager mAppOps;
private final TelephonyManager mTelephonyManager;
private final ArraySet<String> mBugreportWhitelistedPackages;
BugreportManagerServiceImpl(Context context) {
mContext = context;
mAppOps = (AppOpsManager) context.getSystemService(Context.APP_OPS_SERVICE);
mAppOps = context.getSystemService(AppOpsManager.class);
mTelephonyManager = context.getSystemService(TelephonyManager.class);
mBugreportWhitelistedPackages =
SystemConfig.getInstance().getBugreportWhitelistedPackages();
}
@@ -67,11 +71,14 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
public void startBugreport(int callingUidUnused, String callingPackage,
FileDescriptor bugreportFd, FileDescriptor screenshotFd,
int bugreportMode, IDumpstateListener listener, boolean isScreenshotRequested) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP, "startBugreport");
Objects.requireNonNull(callingPackage);
Objects.requireNonNull(bugreportFd);
Objects.requireNonNull(listener);
validateBugreportMode(bugreportMode);
int callingUid = Binder.getCallingUid();
enforcePermission(callingPackage, callingUid, bugreportMode
== BugreportParams.BUGREPORT_MODE_TELEPHONY /* checkCarrierPrivileges */);
final long identity = Binder.clearCallingIdentity();
try {
ensureIsPrimaryUser();
@@ -79,13 +86,6 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
Binder.restoreCallingIdentity(identity);
}
int callingUid = Binder.getCallingUid();
mAppOps.checkPackage(callingUid, callingPackage);
if (!mBugreportWhitelistedPackages.contains(callingPackage)) {
throw new SecurityException(
callingPackage + " is not whitelisted to use Bugreport API");
}
synchronized (mLock) {
startBugreportLocked(callingUid, callingPackage, bugreportFd, screenshotFd,
bugreportMode, listener, isScreenshotRequested);
@@ -93,12 +93,10 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
@Override
@RequiresPermission(android.Manifest.permission.DUMP)
@RequiresPermission(android.Manifest.permission.DUMP) // or carrier privileges
public void cancelBugreport(int callingUidUnused, String callingPackage) {
mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
"cancelBugreport");
int callingUid = Binder.getCallingUid();
mAppOps.checkPackage(callingUid, callingPackage);
enforcePermission(callingPackage, callingUid, true /* checkCarrierPrivileges */);
synchronized (mLock) {
IDumpstate ds = getDumpstateBinderServiceLocked();
@@ -134,6 +132,34 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
}
}
private void enforcePermission(
String callingPackage, int callingUid, boolean checkCarrierPrivileges) {
mAppOps.checkPackage(callingUid, callingPackage);
// To gain access through the DUMP permission, the OEM has to allow this package explicitly
// via sysconfig and privileged permissions.
if (mBugreportWhitelistedPackages.contains(callingPackage)
&& mContext.checkCallingOrSelfPermission(android.Manifest.permission.DUMP)
== PackageManager.PERMISSION_GRANTED) {
return;
}
// For carrier privileges, this can include user-installed apps. This is essentially a
// function of the current active SIM(s) in the device to let carrier apps through.
if (checkCarrierPrivileges
&& mTelephonyManager.checkCarrierPrivilegesForPackageAnyPhone(callingPackage)
== TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS) {
return;
}
String message =
callingPackage
+ " does not hold the DUMP permission or is not bugreport-whitelisted "
+ (checkCarrierPrivileges ? "and does not have carrier privileges " : "")
+ "to request a bugreport";
Slog.w(TAG, message);
throw new SecurityException(message);
}
/**
* Validates that the current user is the primary user.
*