Merge "Add permissions data validation in AppOpsService" into rvc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
1a023125c9
@@ -385,6 +385,15 @@ public class AppOpsManager {
|
|||||||
*/
|
*/
|
||||||
public static final int WATCH_FOREGROUND_CHANGES = 1 << 0;
|
public static final int WATCH_FOREGROUND_CHANGES = 1 << 0;
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flag to determine whether we should log noteOp/startOp calls to make sure they
|
||||||
|
* are correctly used
|
||||||
|
*
|
||||||
|
* @hide
|
||||||
|
*/
|
||||||
|
public static final boolean NOTE_OP_COLLECTION_ENABLED = false;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @hide
|
* @hide
|
||||||
*/
|
*/
|
||||||
@@ -7103,6 +7112,7 @@ public class AppOpsManager {
|
|||||||
public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
|
public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
|
||||||
@Nullable String featureId, @Nullable String message) {
|
@Nullable String featureId, @Nullable String message) {
|
||||||
try {
|
try {
|
||||||
|
collectNoteOpCallsForValidation(op);
|
||||||
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
|
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
|
||||||
if (collectionMode == COLLECT_ASYNC) {
|
if (collectionMode == COLLECT_ASYNC) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
@@ -7263,6 +7273,7 @@ public class AppOpsManager {
|
|||||||
int myUid = Process.myUid();
|
int myUid = Process.myUid();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
collectNoteOpCallsForValidation(op);
|
||||||
int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, op);
|
int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, op);
|
||||||
if (collectionMode == COLLECT_ASYNC) {
|
if (collectionMode == COLLECT_ASYNC) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
@@ -7583,6 +7594,7 @@ public class AppOpsManager {
|
|||||||
public int startOpNoThrow(int op, int uid, @NonNull String packageName,
|
public int startOpNoThrow(int op, int uid, @NonNull String packageName,
|
||||||
boolean startIfModeDefault, @Nullable String featureId, @Nullable String message) {
|
boolean startIfModeDefault, @Nullable String featureId, @Nullable String message) {
|
||||||
try {
|
try {
|
||||||
|
collectNoteOpCallsForValidation(op);
|
||||||
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
|
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
|
||||||
if (collectionMode == COLLECT_ASYNC) {
|
if (collectionMode == COLLECT_ASYNC) {
|
||||||
if (message == null) {
|
if (message == null) {
|
||||||
@@ -8492,4 +8504,24 @@ public class AppOpsManager {
|
|||||||
public static int leftCircularDistance(int from, int to, int size) {
|
public static int leftCircularDistance(int from, int to, int size) {
|
||||||
return (to + size - from) % size;
|
return (to + size - from) % size;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper method for noteOp, startOp and noteProxyOp to call AppOpsService to collect/log
|
||||||
|
* stack traces
|
||||||
|
*
|
||||||
|
* <p> For each call, the stacktrace op code, package name and long version code will be
|
||||||
|
* passed along where it will be logged/collected
|
||||||
|
*
|
||||||
|
* @param op The operation to note
|
||||||
|
*/
|
||||||
|
private void collectNoteOpCallsForValidation(int op) {
|
||||||
|
if (NOTE_OP_COLLECTION_ENABLED) {
|
||||||
|
try {
|
||||||
|
mService.collectNoteOpCallsForValidation(getFormattedStackTrace(),
|
||||||
|
op, mContext.getOpPackageName(), mContext.getApplicationInfo().longVersionCode);
|
||||||
|
} catch (RemoteException e) {
|
||||||
|
// Swallow error, only meant for logging ops, should not affect flow of the code
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -103,4 +103,6 @@ interface IAppOpsService {
|
|||||||
int checkOperationRaw(int code, int uid, String packageName);
|
int checkOperationRaw(int code, int uid, String packageName);
|
||||||
|
|
||||||
void reloadNonHistoricalState();
|
void reloadNonHistoricalState();
|
||||||
|
|
||||||
|
void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName, long version);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -138,6 +138,7 @@ import android.util.TimeUtils;
|
|||||||
import android.util.Xml;
|
import android.util.Xml;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
import com.android.internal.annotations.GuardedBy;
|
||||||
|
import com.android.internal.annotations.Immutable;
|
||||||
import com.android.internal.annotations.VisibleForTesting;
|
import com.android.internal.annotations.VisibleForTesting;
|
||||||
import com.android.internal.app.IAppOpsActiveCallback;
|
import com.android.internal.app.IAppOpsActiveCallback;
|
||||||
import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
import com.android.internal.app.IAppOpsAsyncNotedCallback;
|
||||||
@@ -155,11 +156,14 @@ import com.android.internal.util.function.pooled.PooledLambda;
|
|||||||
import com.android.server.LocalServices;
|
import com.android.server.LocalServices;
|
||||||
import com.android.server.LockGuard;
|
import com.android.server.LockGuard;
|
||||||
import com.android.server.SystemServerInitThreadPool;
|
import com.android.server.SystemServerInitThreadPool;
|
||||||
|
import com.android.server.SystemServiceManager;
|
||||||
import com.android.server.pm.PackageList;
|
import com.android.server.pm.PackageList;
|
||||||
import com.android.server.pm.parsing.pkg.AndroidPackage;
|
import com.android.server.pm.parsing.pkg.AndroidPackage;
|
||||||
|
|
||||||
import libcore.util.EmptyArray;
|
import libcore.util.EmptyArray;
|
||||||
|
|
||||||
|
import org.json.JSONException;
|
||||||
|
import org.json.JSONObject;
|
||||||
import org.xmlpull.v1.XmlPullParser;
|
import org.xmlpull.v1.XmlPullParser;
|
||||||
import org.xmlpull.v1.XmlPullParserException;
|
import org.xmlpull.v1.XmlPullParserException;
|
||||||
import org.xmlpull.v1.XmlSerializer;
|
import org.xmlpull.v1.XmlSerializer;
|
||||||
@@ -169,6 +173,7 @@ import java.io.FileDescriptor;
|
|||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
import java.io.FileNotFoundException;
|
import java.io.FileNotFoundException;
|
||||||
import java.io.FileOutputStream;
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.FileWriter;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.PrintWriter;
|
import java.io.PrintWriter;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
@@ -184,6 +189,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
|
import java.util.Scanner;
|
||||||
import java.util.concurrent.ThreadLocalRandom;
|
import java.util.concurrent.ThreadLocalRandom;
|
||||||
import java.util.function.Consumer;
|
import java.util.function.Consumer;
|
||||||
|
|
||||||
@@ -191,6 +197,11 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
static final String TAG = "AppOps";
|
static final String TAG = "AppOps";
|
||||||
static final boolean DEBUG = false;
|
static final boolean DEBUG = false;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Used for data access validation collection, we wish to only log a specific access once
|
||||||
|
*/
|
||||||
|
private final ArraySet<NoteOpTrace> mNoteOpCallerStacktraces = new ArraySet<>();
|
||||||
|
|
||||||
private static final int NO_VERSION = -1;
|
private static final int NO_VERSION = -1;
|
||||||
/** Increment by one every time and add the corresponding upgrade logic in
|
/** Increment by one every time and add the corresponding upgrade logic in
|
||||||
* {@link #upgradeLocked(int)} below. The first version was 1 */
|
* {@link #upgradeLocked(int)} below. The first version was 1 */
|
||||||
@@ -241,6 +252,7 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
|
|
||||||
final Context mContext;
|
final Context mContext;
|
||||||
final AtomicFile mFile;
|
final AtomicFile mFile;
|
||||||
|
private final @Nullable File mNoteOpCallerStacktracesFile;
|
||||||
final Handler mHandler;
|
final Handler mHandler;
|
||||||
|
|
||||||
/** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */
|
/** Pool for {@link OpEventProxyInfoPool} to avoid to constantly reallocate new objects */
|
||||||
@@ -278,6 +290,8 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
|
private final ArrayMap<Pair<String, Integer>, ArrayList<AsyncNotedAppOp>>
|
||||||
mUnforwardedAsyncNotedOps = new ArrayMap<>();
|
mUnforwardedAsyncNotedOps = new ArrayMap<>();
|
||||||
|
|
||||||
|
boolean mWriteNoteOpsScheduled;
|
||||||
|
|
||||||
boolean mWriteScheduled;
|
boolean mWriteScheduled;
|
||||||
boolean mFastWriteScheduled;
|
boolean mFastWriteScheduled;
|
||||||
final Runnable mWriteRunner = new Runnable() {
|
final Runnable mWriteRunner = new Runnable() {
|
||||||
@@ -1397,11 +1411,42 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
featureOp.onClientDeath(clientId);
|
featureOp.onClientDeath(clientId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads the OpsValidation file results into a hashmap {@link #mNoteOpCallerStacktraces}
|
||||||
|
* so that we do not log the same operation twice between instances
|
||||||
|
*/
|
||||||
|
private void readNoteOpCallerStackTraces() {
|
||||||
|
try {
|
||||||
|
if (!mNoteOpCallerStacktracesFile.exists()) {
|
||||||
|
mNoteOpCallerStacktracesFile.createNewFile();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Scanner read = new Scanner(mNoteOpCallerStacktracesFile)) {
|
||||||
|
read.useDelimiter("\\},");
|
||||||
|
while (read.hasNext()) {
|
||||||
|
String jsonOps = read.next();
|
||||||
|
mNoteOpCallerStacktraces.add(NoteOpTrace.fromJson(jsonOps));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
Slog.e(TAG, "Cannot parse traces noteOps", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public AppOpsService(File storagePath, Handler handler, Context context) {
|
public AppOpsService(File storagePath, Handler handler, Context context) {
|
||||||
mContext = context;
|
mContext = context;
|
||||||
|
|
||||||
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
|
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
|
||||||
mFile = new AtomicFile(storagePath, "appops");
|
mFile = new AtomicFile(storagePath, "appops");
|
||||||
|
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
|
||||||
|
mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
|
||||||
|
"noteOpStackTraces.json");
|
||||||
|
readNoteOpCallerStackTraces();
|
||||||
|
} else {
|
||||||
|
mNoteOpCallerStacktracesFile = null;
|
||||||
|
}
|
||||||
mHandler = handler;
|
mHandler = handler;
|
||||||
mConstants = new Constants(mHandler);
|
mConstants = new Constants(mHandler);
|
||||||
readState();
|
readState();
|
||||||
@@ -1802,6 +1847,9 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
if (doWrite) {
|
if (doWrite) {
|
||||||
writeState();
|
writeState();
|
||||||
}
|
}
|
||||||
|
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
|
||||||
|
writeNoteOps();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
|
private ArrayList<AppOpsManager.OpEntry> collectOps(Ops pkgOps, int[] ops) {
|
||||||
@@ -6051,4 +6099,142 @@ public class AppOpsService extends IAppOpsService.Stub {
|
|||||||
setMode(code, uid, packageName, mode, callback);
|
setMode(code, uid, packageName, mode, callback);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Async task for writing note op stack trace, op code, package name and version to file
|
||||||
|
* More specifically, writes all the collected ops from {@link #mNoteOpCallerStacktraces}
|
||||||
|
*/
|
||||||
|
private void writeNoteOps() {
|
||||||
|
synchronized (this) {
|
||||||
|
mWriteNoteOpsScheduled = false;
|
||||||
|
}
|
||||||
|
synchronized (mNoteOpCallerStacktracesFile) {
|
||||||
|
try (FileWriter writer = new FileWriter(mNoteOpCallerStacktracesFile)) {
|
||||||
|
int numTraces = mNoteOpCallerStacktraces.size();
|
||||||
|
for (int i = 0; i < numTraces; i++) {
|
||||||
|
// Writing json formatted string into file
|
||||||
|
writer.write(mNoteOpCallerStacktraces.valueAt(i).asJson());
|
||||||
|
// Comma separation, so we can wrap the entire log as a JSON object
|
||||||
|
// when all results are collected
|
||||||
|
writer.write(",");
|
||||||
|
}
|
||||||
|
} catch (IOException e) {
|
||||||
|
Slog.w(TAG, "Failed to load opsValidation file for FileWriter", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class represents a NoteOp Trace object amd contains the necessary fields that will
|
||||||
|
* be written to file to use for permissions data validation in JSON format
|
||||||
|
*/
|
||||||
|
@Immutable
|
||||||
|
static class NoteOpTrace {
|
||||||
|
static final String STACKTRACE = "stackTrace";
|
||||||
|
static final String OP = "op";
|
||||||
|
static final String PACKAGENAME = "packageName";
|
||||||
|
static final String VERSION = "version";
|
||||||
|
|
||||||
|
private final @NonNull String mStackTrace;
|
||||||
|
private final int mOp;
|
||||||
|
private final @Nullable String mPackageName;
|
||||||
|
private final long mVersion;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize a NoteOp object using a JSON object containing the necessary fields
|
||||||
|
*
|
||||||
|
* @param jsonTrace JSON object represented as a string
|
||||||
|
*
|
||||||
|
* @return NoteOpTrace object initialized with JSON fields
|
||||||
|
*/
|
||||||
|
static NoteOpTrace fromJson(String jsonTrace) {
|
||||||
|
try {
|
||||||
|
// Re-add closing bracket which acted as a delimiter by the reader
|
||||||
|
JSONObject obj = new JSONObject(jsonTrace.concat("}"));
|
||||||
|
return new NoteOpTrace(obj.getString(STACKTRACE), obj.getInt(OP),
|
||||||
|
obj.getString(PACKAGENAME), obj.getLong(VERSION));
|
||||||
|
} catch (JSONException e) {
|
||||||
|
// Swallow error, only meant for logging ops, should not affect flow of the code
|
||||||
|
Slog.e(TAG, "Error constructing NoteOpTrace object "
|
||||||
|
+ "JSON trace format incorrect", e);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
NoteOpTrace(String stackTrace, int op, String packageName, long version) {
|
||||||
|
mStackTrace = stackTrace;
|
||||||
|
mOp = op;
|
||||||
|
mPackageName = packageName;
|
||||||
|
mVersion = version;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(Object o) {
|
||||||
|
if (this == o) return true;
|
||||||
|
if (o == null || getClass() != o.getClass()) return false;
|
||||||
|
NoteOpTrace that = (NoteOpTrace) o;
|
||||||
|
return mOp == that.mOp
|
||||||
|
&& mVersion == that.mVersion
|
||||||
|
&& mStackTrace.equals(that.mStackTrace)
|
||||||
|
&& Objects.equals(mPackageName, that.mPackageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return Objects.hash(mStackTrace, mOp, mPackageName, mVersion);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The object is formatted as a JSON object and returned as a String
|
||||||
|
*
|
||||||
|
* @return JSON formatted string
|
||||||
|
*/
|
||||||
|
public String asJson() {
|
||||||
|
return "{"
|
||||||
|
+ "\"" + STACKTRACE + "\":\"" + mStackTrace.replace("\n", "\\n")
|
||||||
|
+ '\"' + ",\"" + OP + "\":" + mOp
|
||||||
|
+ ",\"" + PACKAGENAME + "\":\"" + mPackageName + '\"'
|
||||||
|
+ ",\"" + VERSION + "\":" + mVersion
|
||||||
|
+ '}';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Collects noteOps, noteProxyOps and startOps from AppOpsManager and writes it into a file
|
||||||
|
* which will be used for permissions data validation, the given parameters to this method
|
||||||
|
* will be logged in json format
|
||||||
|
*
|
||||||
|
* @param stackTrace stacktrace from the most recent call in AppOpsManager
|
||||||
|
* @param op op code
|
||||||
|
* @param packageName package making call
|
||||||
|
* @param version android version for this call
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName,
|
||||||
|
long version) {
|
||||||
|
if (!AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Objects.requireNonNull(stackTrace);
|
||||||
|
Preconditions.checkArgument(op >= 0);
|
||||||
|
Preconditions.checkArgument(op < AppOpsManager._NUM_OP);
|
||||||
|
Objects.requireNonNull(version);
|
||||||
|
|
||||||
|
NoteOpTrace noteOpTrace = new NoteOpTrace(stackTrace, op, packageName, version);
|
||||||
|
|
||||||
|
boolean noteOpSetWasChanged;
|
||||||
|
synchronized (this) {
|
||||||
|
noteOpSetWasChanged = mNoteOpCallerStacktraces.add(noteOpTrace);
|
||||||
|
if (noteOpSetWasChanged && !mWriteNoteOpsScheduled) {
|
||||||
|
mWriteNoteOpsScheduled = true;
|
||||||
|
mHandler.postDelayed(PooledLambda.obtainRunnable((that) -> {
|
||||||
|
AsyncTask.execute(() -> {
|
||||||
|
that.writeNoteOps();
|
||||||
|
});
|
||||||
|
}, this), 2500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user