Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 6d6c5ec5 authored by Treehugger Robot's avatar Treehugger Robot Committed by Automerger Merge Worker
Browse files

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

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

MUST ONLY BE SUBMITTED BY AUTOMERGER

Change-Id: I9e835752e37bcb99e35078025bb43fb46313833f
parents b581010b 0dcf4de7
Loading
Loading
Loading
Loading
+19 −0
Original line number Diff line number Diff line
@@ -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();
+0 −15
Original line number Diff line number Diff line
@@ -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();
+0 −2
Original line number Diff line number Diff line
@@ -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";

    /**
+112 −66
Original line number Diff line number Diff line
@@ -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. */
    public abstract static class BugreportCallback {
        /**
     * An interface describing the callback for bugreport progress and status.
         * Possible error codes taking a bugreport can encounter.
         *
         * @hide
         */
    public abstract static class BugreportCallback {
        /** @hide */
        @Retention(RetentionPolicy.SOURCE)
        @IntDef(prefix = { "BUGREPORT_ERROR_" }, value = {
        @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 */
        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 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
                        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);
            }
+39 −13
Original line number Diff line number Diff line
@@ -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.
     *