1 /* 2 * Copyright (C) 2024 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 package com.android.server.appop; 18 19 import static android.app.AppOpsManager.OP_ACCESS_ACCESSIBILITY; 20 import static android.app.AppOpsManager.OP_ACCESS_NOTIFICATIONS; 21 import static android.app.AppOpsManager.OP_BIND_ACCESSIBILITY_SERVICE; 22 import static android.app.AppOpsManager.OP_CAMERA; 23 import static android.app.AppOpsManager.OP_COARSE_LOCATION; 24 import static android.app.AppOpsManager.OP_EMERGENCY_LOCATION; 25 import static android.app.AppOpsManager.OP_FINE_LOCATION; 26 import static android.app.AppOpsManager.OP_FLAG_SELF; 27 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXIED; 28 import static android.app.AppOpsManager.OP_FLAG_TRUSTED_PROXY; 29 import static android.app.AppOpsManager.OP_GPS; 30 import static android.app.AppOpsManager.OP_MONITOR_HIGH_POWER_LOCATION; 31 import static android.app.AppOpsManager.OP_MONITOR_LOCATION; 32 import static android.app.AppOpsManager.OP_PHONE_CALL_CAMERA; 33 import static android.app.AppOpsManager.OP_PHONE_CALL_MICROPHONE; 34 import static android.app.AppOpsManager.OP_READ_DEVICE_IDENTIFIERS; 35 import static android.app.AppOpsManager.OP_READ_HEART_RATE; 36 import static android.app.AppOpsManager.OP_READ_OXYGEN_SATURATION; 37 import static android.app.AppOpsManager.OP_READ_SKIN_TEMPERATURE; 38 import static android.app.AppOpsManager.OP_RECEIVE_AMBIENT_TRIGGER_AUDIO; 39 import static android.app.AppOpsManager.OP_RECEIVE_SANDBOX_TRIGGER_AUDIO; 40 import static android.app.AppOpsManager.OP_RECORD_AUDIO; 41 import static android.app.AppOpsManager.OP_RESERVED_FOR_TESTING; 42 import static android.app.AppOpsManager.OP_RUN_IN_BACKGROUND; 43 44 import static java.lang.Long.min; 45 import static java.lang.Math.max; 46 47 import android.annotation.NonNull; 48 import android.annotation.Nullable; 49 import android.app.AppOpsManager; 50 import android.os.AsyncTask; 51 import android.os.Build; 52 import android.permission.flags.Flags; 53 import android.provider.DeviceConfig; 54 import android.util.IntArray; 55 import android.util.Slog; 56 57 import java.io.PrintWriter; 58 import java.text.SimpleDateFormat; 59 import java.time.Duration; 60 import java.util.Arrays; 61 import java.util.Date; 62 import java.util.Set; 63 64 /** 65 * This class provides interface for xml and sqlite implementation. Implementation manages 66 * information about recent accesses to ops for permission usage timeline. 67 * <p> 68 * The discrete history is kept for limited time (initial default is 24 hours, set in 69 * {@link DiscreteOpsRegistry#sDiscreteHistoryCutoff} and discarded after that. 70 * <p> 71 * Discrete history is quantized to reduce resources footprint. By default, quantization is set to 72 * one minute in {@link DiscreteOpsRegistry#sDiscreteHistoryQuantization}. All access times are 73 * aligned to the closest quantized time. All durations (except -1, meaning no duration) are 74 * rounded up to the closest quantized interval. 75 * <p> 76 * When data is queried through API, events are deduplicated and for every time quant there can 77 * be only one {@link AppOpsManager.AttributedOpEntry}. Each entry contains information about 78 * different accesses which happened in specified time quant - across dimensions of 79 * {@link AppOpsManager.UidState} and {@link AppOpsManager.OpFlags}. For each dimension 80 * it is only possible to know if at least one access happened in the time quant. 81 * <p> 82 * INITIALIZATION: We can initialize persistence only after the system is ready 83 * as we need to check the optional configuration override from the settings 84 * database which is not initialized at the time the app ops service is created. This class 85 * relies on {@link LegacyHistoricalRegistry} for controlling that no calls are allowed until then. 86 * All outside calls are going through {@link LegacyHistoricalRegistry}. 87 */ 88 abstract class DiscreteOpsRegistry { 89 private static final String TAG = DiscreteOpsRegistry.class.getSimpleName(); 90 91 static final boolean DEBUG_LOG = false; 92 static final String PROPERTY_DISCRETE_HISTORY_CUTOFF = "discrete_history_cutoff_millis"; 93 static final String PROPERTY_DISCRETE_HISTORY_QUANTIZATION = 94 "discrete_history_quantization_millis"; 95 static final String PROPERTY_DISCRETE_FLAGS = "discrete_history_op_flags"; 96 // Comma separated app ops list config for testing i.e. "1,2,3,4" 97 static final String PROPERTY_DISCRETE_OPS_LIST = "discrete_history_ops_cslist"; 98 // These ops are deemed important for detecting a malicious app, and are recorded. 99 static final int[] IMPORTANT_OPS_FOR_SECURITY = new int[] { 100 OP_GPS, 101 OP_ACCESS_NOTIFICATIONS, 102 OP_RUN_IN_BACKGROUND, 103 OP_BIND_ACCESSIBILITY_SERVICE, 104 OP_ACCESS_ACCESSIBILITY, 105 OP_READ_DEVICE_IDENTIFIERS, 106 OP_MONITOR_HIGH_POWER_LOCATION, 107 OP_MONITOR_LOCATION 108 }; 109 110 // These are additional ops, which are not backed by runtime permissions, but are recorded. 111 static final int[] ADDITIONAL_DISCRETE_OPS = new int[] { 112 OP_PHONE_CALL_MICROPHONE, 113 OP_RECEIVE_AMBIENT_TRIGGER_AUDIO, 114 OP_RECEIVE_SANDBOX_TRIGGER_AUDIO, 115 OP_PHONE_CALL_CAMERA, 116 OP_EMERGENCY_LOCATION, 117 OP_RESERVED_FOR_TESTING 118 }; 119 120 // Legacy ops captured in discrete database. 121 private static final String LEGACY_OPS = OP_FINE_LOCATION + "," + OP_COARSE_LOCATION 122 + "," + OP_EMERGENCY_LOCATION + "," + OP_CAMERA + "," + OP_RECORD_AUDIO + "," 123 + OP_PHONE_CALL_MICROPHONE + "," + OP_PHONE_CALL_CAMERA + "," 124 + OP_RECEIVE_AMBIENT_TRIGGER_AUDIO + "," + OP_RECEIVE_SANDBOX_TRIGGER_AUDIO 125 + "," + OP_READ_HEART_RATE + "," + OP_READ_OXYGEN_SATURATION + "," 126 + OP_READ_SKIN_TEMPERATURE + "," + OP_RESERVED_FOR_TESTING; 127 128 static final long DEFAULT_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(7).toMillis(); 129 static final long MAXIMUM_DISCRETE_HISTORY_CUTOFF = Duration.ofDays(30).toMillis(); 130 // The duration for which the data is kept, default is 7 days and max 30 days enforced. 131 static long sDiscreteHistoryCutoff; 132 133 static final long DEFAULT_DISCRETE_HISTORY_QUANTIZATION = Duration.ofMinutes(1).toMillis(); 134 // discrete ops are rounded up to quantization time, meaning we record one op per time bucket 135 // in case of duplicate op events. 136 static long sDiscreteHistoryQuantization; 137 138 static int[] sDiscreteOps = new int[0]; 139 static int sDiscreteFlags; 140 141 static final int OP_FLAGS_DISCRETE = OP_FLAG_SELF | OP_FLAG_TRUSTED_PROXIED 142 | OP_FLAG_TRUSTED_PROXY; 143 144 boolean mDebugMode = false; 145 systemReady()146 void systemReady() { 147 DeviceConfig.addOnPropertiesChangedListener(DeviceConfig.NAMESPACE_PRIVACY, 148 AsyncTask.THREAD_POOL_EXECUTOR, (DeviceConfig.Properties p) -> { 149 setDiscreteHistoryParameters(p); 150 }); 151 setDiscreteHistoryParameters(DeviceConfig.getProperties(DeviceConfig.NAMESPACE_PRIVACY)); 152 } 153 recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId)154 abstract void recordDiscreteAccess(int uid, String packageName, @NonNull String deviceId, 155 int op, @Nullable String attributionTag, @AppOpsManager.OpFlags int flags, 156 @AppOpsManager.UidState int uidState, long accessTime, long accessDuration, 157 @AppOpsManager.AttributionFlags int attributionFlags, int attributionChainId); 158 159 /** 160 * A periodic callback from {@link AppOpsService} to flush the in memory events to disk. 161 * The shutdown callback is also plugged into it. 162 * <p> 163 * This method flushes in memory records to disk, and also clears old records from disk. 164 */ writeAndClearOldAccessHistory()165 abstract void writeAndClearOldAccessHistory(); 166 shutdown()167 void shutdown() {} 168 169 /** Remove all discrete op events. */ clearHistory()170 abstract void clearHistory(); 171 172 /** Remove all discrete op events for given UID and package. */ clearHistory(int uid, String packageName)173 abstract void clearHistory(int uid, String packageName); 174 175 /** 176 * Offset access time by given timestamp, new access time would be accessTime - offsetMillis. 177 */ offsetHistory(long offset)178 abstract void offsetHistory(long offset); 179 addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, long beginTimeMillis, long endTimeMillis, @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, Set<String> attributionExemptPkgs)180 abstract void addFilteredDiscreteOpsToHistoricalOps(AppOpsManager.HistoricalOps result, 181 long beginTimeMillis, long endTimeMillis, 182 @AppOpsManager.HistoricalOpsRequestFilter int filter, int uidFilter, 183 @Nullable String packageNameFilter, @Nullable String[] opNamesFilter, 184 @Nullable String attributionTagFilter, @AppOpsManager.OpFlags int flagsFilter, 185 Set<String> attributionExemptPkgs); 186 dump(@onNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, @Nullable String attributionTagFilter, @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, int nDiscreteOps)187 abstract void dump(@NonNull PrintWriter pw, int uidFilter, @Nullable String packageNameFilter, 188 @Nullable String attributionTagFilter, 189 @AppOpsManager.HistoricalOpsRequestFilter int filter, int dumpOp, 190 @NonNull SimpleDateFormat sdf, @NonNull Date date, @NonNull String prefix, 191 int nDiscreteOps); 192 setDebugMode(boolean debugMode)193 void setDebugMode(boolean debugMode) { 194 this.mDebugMode = debugMode; 195 } 196 discretizeTimeStamp(long timeStamp)197 static long discretizeTimeStamp(long timeStamp) { 198 return timeStamp / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; 199 200 } 201 discretizeDuration(long duration)202 static long discretizeDuration(long duration) { 203 return duration == -1 ? -1 : (duration + sDiscreteHistoryQuantization - 1) 204 / sDiscreteHistoryQuantization * sDiscreteHistoryQuantization; 205 } 206 isDiscreteOp(int op, @AppOpsManager.OpFlags int flags)207 static boolean isDiscreteOp(int op, @AppOpsManager.OpFlags int flags) { 208 if (Arrays.binarySearch(sDiscreteOps, op) < 0) { 209 return false; 210 } 211 if ((flags & (sDiscreteFlags)) == 0) { 212 return false; 213 } 214 return true; 215 } 216 setDiscreteHistoryParameters(DeviceConfig.Properties p)217 private void setDiscreteHistoryParameters(DeviceConfig.Properties p) { 218 if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_CUTOFF)) { 219 sDiscreteHistoryCutoff = p.getLong(PROPERTY_DISCRETE_HISTORY_CUTOFF, 220 DEFAULT_DISCRETE_HISTORY_CUTOFF); 221 if (!Build.IS_DEBUGGABLE && !mDebugMode) { 222 sDiscreteHistoryCutoff = min(MAXIMUM_DISCRETE_HISTORY_CUTOFF, 223 sDiscreteHistoryCutoff); 224 } 225 } else { 226 sDiscreteHistoryCutoff = DEFAULT_DISCRETE_HISTORY_CUTOFF; 227 } 228 if (p.getKeyset().contains(PROPERTY_DISCRETE_HISTORY_QUANTIZATION)) { 229 sDiscreteHistoryQuantization = p.getLong(PROPERTY_DISCRETE_HISTORY_QUANTIZATION, 230 DEFAULT_DISCRETE_HISTORY_QUANTIZATION); 231 if (!Build.IS_DEBUGGABLE && !mDebugMode) { 232 sDiscreteHistoryQuantization = max(DEFAULT_DISCRETE_HISTORY_QUANTIZATION, 233 sDiscreteHistoryQuantization); 234 } 235 } else { 236 sDiscreteHistoryQuantization = DEFAULT_DISCRETE_HISTORY_QUANTIZATION; 237 } 238 sDiscreteFlags = p.getKeyset().contains(PROPERTY_DISCRETE_FLAGS) 239 ? p.getInt(PROPERTY_DISCRETE_FLAGS, OP_FLAGS_DISCRETE) : OP_FLAGS_DISCRETE; 240 String opsListConfig = p.getString(PROPERTY_DISCRETE_OPS_LIST, null); 241 sDiscreteOps = opsListConfig == null ? getDefaultOpsList() : parseOpsList(opsListConfig); 242 243 Arrays.sort(sDiscreteOps); 244 } 245 246 // App ops backed by runtime/dangerous permissions. getRuntimePermissionOps()247 private static IntArray getRuntimePermissionOps() { 248 IntArray runtimeOps = new IntArray(); 249 for (int op = 0; op < AppOpsManager._NUM_OP; op++) { 250 if (AppOpsManager.opIsRuntimePermission(op)) { 251 runtimeOps.add(op); 252 } 253 } 254 return runtimeOps; 255 } 256 257 /** 258 * @return an array of app ops captured into discrete database. 259 */ getDefaultOpsList()260 private static int[] getDefaultOpsList() { 261 if (!(Flags.recordAllRuntimeAppopsSqlite() && Flags.enableSqliteAppopsAccesses())) { 262 return getDefaultLegacyOps(); 263 } 264 265 IntArray discreteOpsArray = getRuntimePermissionOps(); 266 discreteOpsArray.addAll(IMPORTANT_OPS_FOR_SECURITY); 267 discreteOpsArray.addAll(ADDITIONAL_DISCRETE_OPS); 268 269 return discreteOpsArray.toArray(); 270 } 271 getDefaultLegacyOps()272 private static int[] getDefaultLegacyOps() { 273 return parseOpsList(LEGACY_OPS); 274 } 275 parseOpsList(String opsList)276 private static int[] parseOpsList(String opsList) { 277 String[] strArr; 278 if (opsList.isEmpty()) { 279 strArr = new String[0]; 280 } else { 281 strArr = opsList.split(","); 282 } 283 int nOps = strArr.length; 284 int[] result = new int[nOps]; 285 try { 286 for (int i = 0; i < nOps; i++) { 287 result[i] = Integer.parseInt(strArr[i]); 288 } 289 } catch (NumberFormatException e) { 290 Slog.e(TAG, "Failed to parse Discrete ops list: " + e.getMessage()); 291 return getDefaultOpsList(); 292 } 293 return result; 294 } 295 } 296