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

Commit 05005e9a authored by Kholoud Mohamed's avatar Kholoud Mohamed
Browse files

Add new bugreport retrieval capabilities

Added the following functionalaity:
- Allow apps holding INTERACT_ACROSS_USER to retrieve the bugreport
  requested by the same app in a different user
- Add ability to retrieve the same bugreport multiple times
- Add ability for app to reattempt retrieve the bugreport if it was not
  conseted by the user the first time.

Bug: 302517677
Bug: 304272173
Test: manual testing
Test: atest CtsRootBugreportTestCases
Test: BugreportManagerServiceImplTest
Change-Id: I40bad3e1bf066348f073910e34319229ed05978e
parent 721379d1
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -2148,7 +2148,9 @@ package android.os {
  }

  public final class BugreportParams {
    field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL = 4; // 0x4
    field @FlaggedApi("android.os.bugreport_mode_max_value") public static final int BUGREPORT_MODE_MAX_VALUE = 7; // 0x7
    field @FlaggedApi("android.app.admin.flags.onboarding_bugreport_v2_enabled") public static final int BUGREPORT_MODE_ONBOARDING = 7; // 0x7
  }

  public class Build {
+6 −2
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import android.annotation.RequiresPermission;
import android.annotation.SuppressAutoDoc;
import android.annotation.SystemApi;
import android.annotation.SystemService;
import android.annotation.UserHandleAware;
import android.annotation.WorkerThread;
import android.app.ActivityManager;
import android.content.Context;
@@ -280,8 +281,8 @@ public final class BugreportManager {
     *
     * <p>{@link BugreportManager} takes ownership of {@code bugreportFd}.
     *
     * <p>The caller may only request to retrieve a given bugreport once. Subsequent calls will fail
     * with error code {@link BugreportCallback#BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE}.
     * <p>The caller can reattempt to retrieve the bugreport multiple times if the user has not
     * consented on previous attempts.
     *
     * @param bugreportFile the identifier for a bugreport that was previously generated for this
     *      caller using {@code startBugreport}.
@@ -294,6 +295,7 @@ public final class BugreportManager {
    @SystemApi
    @RequiresPermission(Manifest.permission.DUMP)
    @WorkerThread
    @UserHandleAware
    public void retrieveBugreport(
            @NonNull String bugreportFile,
            @NonNull ParcelFileDescriptor bugreportFd,
@@ -307,8 +309,10 @@ public final class BugreportManager {
            Preconditions.checkNotNull(callback);
            DumpstateListener dsListener = new DumpstateListener(executor, callback, false, false);
            mBinder.retrieveBugreport(Binder.getCallingUid(), mContext.getOpPackageName(),
                    mContext.getUserId(),
                    bugreportFd.getFileDescriptor(),
                    bugreportFile,
                    /* keepBugreportOnRetrieval = */ false,
                    dsListener);
        } catch (RemoteException e) {
            throw e.rethrowFromSystemServer();
+21 −1
Original line number Diff line number Diff line
@@ -20,6 +20,7 @@ import android.annotation.FlaggedApi;
import android.annotation.IntDef;
import android.annotation.SystemApi;
import android.annotation.TestApi;
import android.app.admin.flags.Flags;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
@@ -128,6 +129,8 @@ public final class BugreportParams {
     *
     * @hide
     */
    @TestApi
    @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
    public static final int BUGREPORT_MODE_ONBOARDING = IDumpstate.BUGREPORT_MODE_ONBOARDING;

    /**
@@ -145,7 +148,8 @@ public final class BugreportParams {
    @Retention(RetentionPolicy.SOURCE)
    @IntDef(flag = true, prefix = { "BUGREPORT_FLAG_" }, value = {
            BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA,
            BUGREPORT_FLAG_DEFER_CONSENT
            BUGREPORT_FLAG_DEFER_CONSENT,
            BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL
    })
    public @interface BugreportFlag {}

@@ -165,4 +169,20 @@ public final class BugreportParams {
     * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
     */
    public static final int BUGREPORT_FLAG_DEFER_CONSENT = IDumpstate.BUGREPORT_FLAG_DEFER_CONSENT;

    /**
     * Flag for keeping a bugreport stored even after it has been retrieved via
     * {@link BugreportManager#retrieveBugreport}.
     *
     * <p>This flag can only be used when {@link #BUGREPORT_FLAG_DEFER_CONSENT} is set.
     * The bugreport may be retrieved multiple times using
     * {@link BugreportManager#retrieveBugreport(
     * String, ParcelFileDescriptor, Executor, BugreportManager.BugreportCallback)}.
     *
     * @hide
     */
    @TestApi
    @FlaggedApi(Flags.FLAG_ONBOARDING_BUGREPORT_V2_ENABLED)
    public static final int BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL =
            IDumpstate.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL;
}
+1 −0
Original line number Diff line number Diff line
@@ -155,6 +155,7 @@ java_library_static {
        "service-permission.stubs.system_server",
        "service-rkp.stubs.system_server",
        "service-sdksandbox.stubs.system_server",
        "device_policy_aconfig_flags_lib",
    ],
    plugins: ["ImmutabilityAnnotationProcessor"],

+91 −22
Original line number Diff line number Diff line
@@ -16,6 +16,8 @@

package com.android.server.os;

import static android.app.admin.flags.Flags.onboardingBugreportV2Enabled;

import android.Manifest;
import android.annotation.Nullable;
import android.annotation.RequiresPermission;
@@ -52,8 +54,10 @@ import com.android.server.utils.Slogf;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.HashSet;
import java.util.Objects;
import java.util.OptionalInt;
import java.util.Set;

/**
 * Implementation of the service that provides a privileged API to capture and consume bugreports.
@@ -101,10 +105,12 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
        private final ArrayMap<Pair<Integer, String>, ArraySet<String>> mBugreportFiles =
                new ArrayMap<>();

        @GuardedBy("mLock")
        private final Set<String> mBugreportFilesToPersist = new HashSet<>();

        /**
         * Checks that a given file was generated on behalf of the given caller. If the file was
         * generated on behalf of the caller, it is removed from the bugreport mapping so that it
         * may not be retrieved again. If the file was not generated on behalf of the caller, an
         * not generated on behalf of the caller, an
         * {@link IllegalArgumentException} is thrown.
         *
         * @param callingInfo a (uid, package name) pair identifying the caller
@@ -114,9 +120,39 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
         * @throws IllegalArgumentException if {@code bugreportFile} is not associated with
         *                                  {@code callingInfo}.
         */
        @RequiresPermission(value = android.Manifest.permission.INTERACT_ACROSS_USERS,
                conditional = true)
        void ensureCallerPreviouslyGeneratedFile(
                Pair<Integer, String> callingInfo, String bugreportFile) {
                Context context, Pair<Integer, String> callingInfo, int userId,
                String bugreportFile) {
            synchronized (mLock) {
                if (onboardingBugreportV2Enabled()) {
                    final int uidForUser = Binder.withCleanCallingIdentity(() -> {
                        try {
                            return context.getPackageManager()
                                    .getPackageUidAsUser(callingInfo.second, userId);
                        } catch (PackageManager.NameNotFoundException exception) {
                            throwInvalidBugreportFileForCallerException(
                                    bugreportFile, callingInfo.second);
                            return -1;
                        }
                    });
                    if (uidForUser != callingInfo.first && context.checkCallingOrSelfPermission(
                            Manifest.permission.INTERACT_ACROSS_USERS)
                            != PackageManager.PERMISSION_GRANTED) {
                        throw new SecurityException(
                                callingInfo.second + " does not hold the "
                                        + "INTERACT_ACROSS_USERS permission to access "
                                        + "cross-user bugreports.");
                    }
                    ArraySet<String> bugreportFilesForUid = mBugreportFiles.get(
                            new Pair<>(uidForUser, callingInfo.second));
                    if (bugreportFilesForUid == null
                            || !bugreportFilesForUid.contains(bugreportFile)) {
                        throwInvalidBugreportFileForCallerException(
                                bugreportFile, callingInfo.second);
                    }
                } else {
                    ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo);
                    if (bugreportFilesForCaller != null
                            && bugreportFilesForCaller.contains(bugreportFile)) {
@@ -125,24 +161,35 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
                            mBugreportFiles.remove(callingInfo);
                        }
                    } else {
                    throw new IllegalArgumentException(
                            "File " + bugreportFile + " was not generated"
                                    + " on behalf of calling package " + callingInfo.second);
                        throwInvalidBugreportFileForCallerException(
                                bugreportFile, callingInfo.second);

                    }
                }
            }
        }

        private static void throwInvalidBugreportFileForCallerException(
                String bugreportFile, String packageName) {
            throw new IllegalArgumentException("File " + bugreportFile + " was not generated on"
                    + " behalf of calling package " + packageName);
        }

        /**
         * Associates a bugreport file with a caller, which is identified as a
         * (uid, package name) pair.
         */
        void addBugreportFileForCaller(Pair<Integer, String> caller, String bugreportFile) {
        void addBugreportFileForCaller(
                Pair<Integer, String> caller, String bugreportFile, boolean keepOnRetrieval) {
            synchronized (mLock) {
                if (!mBugreportFiles.containsKey(caller)) {
                    mBugreportFiles.put(caller, new ArraySet<>());
                }
                ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(caller);
                bugreportFilesForCaller.add(bugreportFile);
                if ((onboardingBugreportV2Enabled()) && keepOnRetrieval) {
                    mBugreportFilesToPersist.add(bugreportFile);
                }
            }
        }
    }
@@ -246,16 +293,17 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
    }

    @Override
    @RequiresPermission(Manifest.permission.DUMP)
    public void retrieveBugreport(int callingUidUnused, String callingPackage,
            FileDescriptor bugreportFd, String bugreportFile, IDumpstateListener listener) {
    @RequiresPermission(value = Manifest.permission.DUMP, conditional = true)
    public void retrieveBugreport(int callingUidUnused, String callingPackage, int userId,
            FileDescriptor bugreportFd, String bugreportFile,
            boolean keepBugreportOnRetrievalUnused, IDumpstateListener listener) {
        int callingUid = Binder.getCallingUid();
        enforcePermission(callingPackage, callingUid, false);

        Slogf.i(TAG, "Retrieving bugreport for %s / %d", callingPackage, callingUid);
        try {
            mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(
                    new Pair<>(callingUid, callingPackage), bugreportFile);
                    mContext, new Pair<>(callingUid, callingPackage), userId, bugreportFile);
        } catch (IllegalArgumentException e) {
            Slog.e(TAG, e.getMessage());
            reportError(listener, IDumpstateListener.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE);
@@ -281,10 +329,17 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
            // Wrap the listener so we can intercept binder events directly.
            DumpstateListener myListener = new DumpstateListener(listener, ds,
                    new Pair<>(callingUid, callingPackage), /* reportFinishedFile= */ true);

            boolean keepBugreportOnRetrieval = false;
            if (onboardingBugreportV2Enabled()) {
                keepBugreportOnRetrieval = mBugreportFileManager.mBugreportFilesToPersist.contains(
                        bugreportFile);
            }

            setCurrentDumpstateListenerLocked(myListener);
            try {
                ds.retrieveBugreport(callingUid, callingPackage, bugreportFd,
                        bugreportFile, myListener);
                ds.retrieveBugreport(callingUid, callingPackage, userId, bugreportFd,
                        bugreportFile, keepBugreportOnRetrieval, myListener);
            } catch (RemoteException e) {
                Slog.e(TAG, "RemoteException in retrieveBugreport", e);
            }
@@ -317,7 +372,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
    private void validateBugreportFlags(int flags) {
        flags = clearBugreportFlag(flags,
                BugreportParams.BUGREPORT_FLAG_USE_PREDUMPED_UI_DATA
                        | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT);
                        | BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT
                        | BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL);
        if (flags != 0) {
            Slog.w(TAG, "Unknown bugreport flags: " + flags);
            throw new IllegalArgumentException("Unknown bugreport flags: " + flags);
@@ -482,6 +538,9 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
        boolean reportFinishedFile =
                (bugreportFlags & BugreportParams.BUGREPORT_FLAG_DEFER_CONSENT) != 0;

        boolean keepBugreportOnRetrieval =
                (bugreportFlags & BugreportParams.BUGREPORT_FLAG_KEEP_BUGREPORT_ON_RETRIEVAL) != 0;

        IDumpstate ds = startAndGetDumpstateBinderServiceLocked();
        if (ds == null) {
            Slog.w(TAG, "Unable to get bugreport service");
@@ -490,7 +549,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
        }

        DumpstateListener myListener = new DumpstateListener(listener, ds,
                new Pair<>(callingUid, callingPackage), reportFinishedFile);
                new Pair<>(callingUid, callingPackage), reportFinishedFile,
                keepBugreportOnRetrieval);
        setCurrentDumpstateListenerLocked(myListener);
        try {
            ds.startBugreport(callingUid, callingPackage, bugreportFd, screenshotFd, bugreportMode,
@@ -646,9 +706,16 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
        private final boolean mReportFinishedFile;
        private int mProgress; // used for debugging purposes only
        private boolean mDone;
        private boolean mKeepBugreportOnRetrieval;

        DumpstateListener(IDumpstateListener listener, IDumpstate ds,
                Pair<Integer, String> caller, boolean reportFinishedFile) {
            this(listener, ds, caller, reportFinishedFile, /* keepBugreportOnRetrieval= */ false);
        }

        DumpstateListener(IDumpstateListener listener, IDumpstate ds,
                Pair<Integer, String> caller, boolean reportFinishedFile,
                boolean keepBugreportOnRetrieval) {
            if (DEBUG) {
                Slogf.d(TAG, "Starting DumpstateListener(id=%d) for caller %s", mId, caller);
            }
@@ -656,6 +723,7 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
            mDs = ds;
            mCaller = caller;
            mReportFinishedFile = reportFinishedFile;
            mKeepBugreportOnRetrieval = keepBugreportOnRetrieval;
            try {
                mDs.asBinder().linkToDeath(this, 0);
            } catch (RemoteException e) {
@@ -690,7 +758,8 @@ class BugreportManagerServiceImpl extends IDumpstate.Stub {
                reportFinishedLocked("File: " + bugreportFile);
            }
            if (mReportFinishedFile) {
                mBugreportFileManager.addBugreportFileForCaller(mCaller, bugreportFile);
                mBugreportFileManager.addBugreportFileForCaller(
                        mCaller, bugreportFile, mKeepBugreportOnRetrieval);
            } else if (DEBUG) {
                Slog.d(TAG, "Not reporting finished file");
            }
Loading