Protect usage data with OP_GET_USAGE_STATS.

APIs that return package usage data (such as DropBoxManager) must
ensure that callers hold both the PACKAGE_USAGE_STATS permission
and the OP_GET_USAGE_STATS app-op.

Bug: 78355661
Test: Search output directory for binaries that have READ_LOGS but not
USAGE_STATS and find none.

Change-Id: I85e3bad680bb510439d73c7db5cc50cdcb7bbb42
diff --git a/config/hiddenapi-light-greylist.txt b/config/hiddenapi-light-greylist.txt
index 8db7de3..9b6f631 100644
--- a/config/hiddenapi-light-greylist.txt
+++ b/config/hiddenapi-light-greylist.txt
@@ -7575,7 +7575,7 @@
 Lcom/android/internal/os/HandlerCaller;->obtainMessageOOO(ILjava/lang/Object;Ljava/lang/Object;Ljava/lang/Object;)Landroid/os/Message;
 Lcom/android/internal/os/HandlerCaller;->sendMessage(Landroid/os/Message;)V
 Lcom/android/internal/os/IDropBoxManagerService$Stub;->asInterface(Landroid/os/IBinder;)Lcom/android/internal/os/IDropBoxManagerService;
-Lcom/android/internal/os/IDropBoxManagerService;->getNextEntry(Ljava/lang/String;J)Landroid/os/DropBoxManager$Entry;
+Lcom/android/internal/os/IDropBoxManagerService;->getNextEntry(Ljava/lang/String;JLjava/lang/String;)Landroid/os/DropBoxManager$Entry;
 Lcom/android/internal/os/PowerProfile;-><init>(Landroid/content/Context;)V
 Lcom/android/internal/os/PowerProfile;->getAveragePower(Ljava/lang/String;)D
 Lcom/android/internal/os/PowerProfile;->getAveragePower(Ljava/lang/String;I)D
diff --git a/core/java/android/os/DropBoxManager.java b/core/java/android/os/DropBoxManager.java
index 97f0e0c..eeae25e 100644
--- a/core/java/android/os/DropBoxManager.java
+++ b/core/java/android/os/DropBoxManager.java
@@ -16,9 +16,14 @@
 
 package android.os;
 
+import static android.Manifest.permission.PACKAGE_USAGE_STATS;
+import static android.Manifest.permission.READ_LOGS;
+
+import android.annotation.Nullable;
+import android.annotation.RequiresPermission;
 import android.annotation.SdkConstant;
-import android.annotation.SystemService;
 import android.annotation.SdkConstant.SdkConstantType;
+import android.annotation.SystemService;
 import android.content.Context;
 import android.util.Log;
 
@@ -351,16 +356,23 @@
 
     /**
      * Gets the next entry from the drop box <em>after</em> the specified time.
-     * Requires <code>android.permission.READ_LOGS</code>.  You must always call
-     * {@link Entry#close()} on the return value!
+     * You must always call {@link Entry#close()} on the return value!
      *
      * @param tag of entry to look for, null for all tags
      * @param msec time of the last entry seen
      * @return the next entry, or null if there are no more entries
      */
-    public Entry getNextEntry(String tag, long msec) {
+    @RequiresPermission(allOf = { READ_LOGS, PACKAGE_USAGE_STATS })
+    public @Nullable Entry getNextEntry(String tag, long msec) {
         try {
-            return mService.getNextEntry(tag, msec);
+            return mService.getNextEntry(tag, msec, mContext.getOpPackageName());
+        } catch (SecurityException e) {
+            if (mContext.getApplicationInfo().targetSdkVersion >= Build.VERSION_CODES.P) {
+                throw e;
+            } else {
+                Log.w(TAG, e.getMessage());
+                return null;
+            }
         } catch (RemoteException e) {
             throw e.rethrowFromSystemServer();
         }
diff --git a/core/java/com/android/internal/os/IDropBoxManagerService.aidl b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
index d16579c..70844ee 100644
--- a/core/java/com/android/internal/os/IDropBoxManagerService.aidl
+++ b/core/java/com/android/internal/os/IDropBoxManagerService.aidl
@@ -37,5 +37,5 @@
     boolean isTagEnabled(String tag);
 
     /** @see DropBoxManager#getNextEntry */
-    DropBoxManager.Entry getNextEntry(String tag, long millis);
+    DropBoxManager.Entry getNextEntry(String tag, long millis, String packageName);
 }
diff --git a/libs/services/src/os/DropBoxManager.cpp b/libs/services/src/os/DropBoxManager.cpp
index c2907a6..8282518 100644
--- a/libs/services/src/os/DropBoxManager.cpp
+++ b/libs/services/src/os/DropBoxManager.cpp
@@ -236,7 +236,7 @@
     if (service == NULL) {
         return Status::fromExceptionCode(Status::EX_NULL_POINTER, "can't find dropbox service");
     }
-    return service->getNextEntry(tag, msec, entry);
+    return service->getNextEntry(tag, msec, android::String16("android"), entry);
 }
 
 }} // namespace android::os
diff --git a/services/core/java/com/android/server/DropBoxManagerService.java b/services/core/java/com/android/server/DropBoxManagerService.java
index 887de74..b393d87 100644
--- a/services/core/java/com/android/server/DropBoxManagerService.java
+++ b/services/core/java/com/android/server/DropBoxManagerService.java
@@ -17,12 +17,12 @@
 package com.android.server;
 
 import android.app.ActivityManager;
+import android.app.AppOpsManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.pm.PackageManager;
 import android.database.ContentObserver;
 import android.net.Uri;
 import android.os.Binder;
@@ -41,13 +41,13 @@
 import android.util.ArrayMap;
 import android.util.Slog;
 
-import libcore.io.IoUtils;
-
 import com.android.internal.annotations.VisibleForTesting;
 import com.android.internal.os.IDropBoxManagerService;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.ObjectUtils;
 
+import libcore.io.IoUtils;
+
 import java.io.BufferedOutputStream;
 import java.io.File;
 import java.io.FileDescriptor;
@@ -58,7 +58,6 @@
 import java.io.OutputStream;
 import java.io.PrintWriter;
 import java.util.ArrayList;
-import java.util.Objects;
 import java.util.SortedSet;
 import java.util.TreeSet;
 import java.util.zip.GZIPOutputStream;
@@ -145,8 +144,8 @@
         }
 
         @Override
-        public DropBoxManager.Entry getNextEntry(String tag, long millis) {
-            return DropBoxManagerService.this.getNextEntry(tag, millis);
+        public DropBoxManager.Entry getNextEntry(String tag, long millis, String callingPackage) {
+            return DropBoxManagerService.this.getNextEntry(tag, millis, callingPackage);
         }
 
         @Override
@@ -327,10 +326,29 @@
         }
     }
 
-    public synchronized DropBoxManager.Entry getNextEntry(String tag, long millis) {
-        if (getContext().checkCallingOrSelfPermission(android.Manifest.permission.READ_LOGS)
-                != PackageManager.PERMISSION_GRANTED) {
-            throw new SecurityException("READ_LOGS permission required");
+    private boolean checkPermission(int callingUid, String callingPackage) {
+        // Callers always need this permission
+        getContext().enforceCallingOrSelfPermission(
+                android.Manifest.permission.READ_LOGS, TAG);
+
+        // Callers also need the ability to read usage statistics
+        switch (getContext().getSystemService(AppOpsManager.class)
+                .noteOp(AppOpsManager.OP_GET_USAGE_STATS, callingUid, callingPackage)) {
+            case AppOpsManager.MODE_ALLOWED:
+                return true;
+            case AppOpsManager.MODE_DEFAULT:
+                getContext().enforceCallingOrSelfPermission(
+                        android.Manifest.permission.PACKAGE_USAGE_STATS, TAG);
+                return true;
+            default:
+                return false;
+        }
+    }
+
+    public synchronized DropBoxManager.Entry getNextEntry(String tag, long millis,
+            String callingPackage) {
+        if (!checkPermission(Binder.getCallingUid(), callingPackage)) {
+            return null;
         }
 
         try {