diff options
| author | 2023-10-27 09:36:07 +0000 | |
|---|---|---|
| committer | 2023-10-27 09:36:07 +0000 | |
| commit | e594a9255205d1f1ca8e27d2a22f25ff61c83e2f (patch) | |
| tree | ebe0f21cbf05a3436d3b1f18f77c7b2dae679289 | |
| parent | 5a84903b7b9ebffd76b2aa3be9a56da881984eba (diff) | |
| parent | 05005e9a9481c2361e1dadb9ee1d01d718439e6e (diff) | |
Merge "Add new bugreport retrieval capabilities" into main
| -rw-r--r-- | core/api/test-current.txt | 2 | ||||
| -rw-r--r-- | core/java/android/os/BugreportManager.java | 8 | ||||
| -rw-r--r-- | core/java/android/os/BugreportParams.java | 22 | ||||
| -rw-r--r-- | services/core/Android.bp | 1 | ||||
| -rw-r--r-- | services/core/java/com/android/server/os/BugreportManagerServiceImpl.java | 113 | ||||
| -rw-r--r-- | services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java | 24 |
6 files changed, 136 insertions, 34 deletions
diff --git a/core/api/test-current.txt b/core/api/test-current.txt index e130206d49fa..83234a5dbce2 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -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 { diff --git a/core/java/android/os/BugreportManager.java b/core/java/android/os/BugreportManager.java index 58def6ef7961..960e84d671e7 100644 --- a/core/java/android/os/BugreportManager.java +++ b/core/java/android/os/BugreportManager.java @@ -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(); diff --git a/core/java/android/os/BugreportParams.java b/core/java/android/os/BugreportParams.java index e8ad303c4208..8510084c309d 100644 --- a/core/java/android/os/BugreportParams.java +++ b/core/java/android/os/BugreportParams.java @@ -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; } diff --git a/services/core/Android.bp b/services/core/Android.bp index 961e9d3dcfff..3985a0e7f46c 100644 --- a/services/core/Android.bp +++ b/services/core/Android.bp @@ -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"], diff --git a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java index 1134714bce55..e57ea0f65804 100644 --- a/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java +++ b/services/core/java/com/android/server/os/BugreportManagerServiceImpl.java @@ -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,35 +120,76 @@ 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) { - ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo); - if (bugreportFilesForCaller != null - && bugreportFilesForCaller.contains(bugreportFile)) { - bugreportFilesForCaller.remove(bugreportFile); - if (bugreportFilesForCaller.isEmpty()) { - mBugreportFiles.remove(callingInfo); + 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 { - throw new IllegalArgumentException( - "File " + bugreportFile + " was not generated" - + " on behalf of calling package " + callingInfo.second); + ArraySet<String> bugreportFilesForCaller = mBugreportFiles.get(callingInfo); + if (bugreportFilesForCaller != null + && bugreportFilesForCaller.contains(bugreportFile)) { + bugreportFilesForCaller.remove(bugreportFile); + if (bugreportFilesForCaller.isEmpty()) { + mBugreportFiles.remove(callingInfo); + } + } else { + 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"); } diff --git a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java index fc27edcb7bda..a4d50f0f9559 100644 --- a/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/os/BugreportManagerServiceImplTest.java @@ -94,27 +94,31 @@ public class BugreportManagerServiceImplTest { public void testBugreportFileManagerFileExists() { Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage); mBugreportFileManager.addBugreportFileForCaller( - callingInfo, mBugreportFile); + callingInfo, mBugreportFile, /* keepOnRetrieval= */ false); assertThrows(IllegalArgumentException.class, () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( - callingInfo, "unknown-file.zip")); + mContext, callingInfo, Process.myUserHandle().getIdentifier(), + "unknown-file.zip")); // No exception should be thrown. - mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile); + mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( + mContext, callingInfo, mContext.getUserId(), mBugreportFile); } @Test public void testBugreportFileManagerMultipleFiles() { Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage); mBugreportFileManager.addBugreportFileForCaller( - callingInfo, mBugreportFile); + callingInfo, mBugreportFile, /* keepOnRetrieval= */ false); mBugreportFileManager.addBugreportFileForCaller( - callingInfo, mBugreportFile2); + callingInfo, mBugreportFile2, /* keepOnRetrieval= */ false); // No exception should be thrown. - mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile); - mBugreportFileManager.ensureCallerPreviouslyGeneratedFile(callingInfo, mBugreportFile2); + mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( + mContext, callingInfo, mContext.getUserId(), mBugreportFile); + mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( + mContext, callingInfo, mContext.getUserId(), mBugreportFile2); } @Test @@ -122,7 +126,8 @@ public class BugreportManagerServiceImplTest { Pair<Integer, String> callingInfo = new Pair<>(mCallingUid, mCallingPackage); assertThrows(IllegalArgumentException.class, () -> mBugreportFileManager.ensureCallerPreviouslyGeneratedFile( - callingInfo, "test-file.zip")); + mContext, callingInfo, Process.myUserHandle().getIdentifier(), + "test-file.zip")); } @Test @@ -130,7 +135,8 @@ public class BugreportManagerServiceImplTest { CountDownLatch latch = new CountDownLatch(1); Listener listener = new Listener(latch); mService.retrieveBugreport(Binder.getCallingUid(), mContext.getPackageName(), - new FileDescriptor(), mBugreportFile, listener); + mContext.getUserId(), new FileDescriptor(), mBugreportFile, + /* keepOnRetrieval= */ false, listener); assertThat(latch.await(5, TimeUnit.SECONDS)).isTrue(); assertThat(listener.getErrorCode()).isEqualTo( BugreportCallback.BUGREPORT_ERROR_NO_BUGREPORT_TO_RETRIEVE); |