Merge "Add permissions data validation in AppOpsService" into rvc-dev am: 1a023125c9

Change-Id: Ic5ac56dc9ab7a2817e72053a142c8384a12aa0bd
This commit is contained in:
Automerger Merge Worker
2020-03-07 01:13:02 +00:00
3 changed files with 220 additions and 0 deletions

View File

@@ -385,6 +385,15 @@ public class AppOpsManager {
*/
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
*/
@@ -7103,6 +7112,7 @@ public class AppOpsManager {
public int noteOpNoThrow(int op, int uid, @Nullable String packageName,
@Nullable String featureId, @Nullable String message) {
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
@@ -7263,6 +7273,7 @@ public class AppOpsManager {
int myUid = Process.myUid();
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(proxiedUid, proxiedPackageName, op);
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
@@ -7583,6 +7594,7 @@ public class AppOpsManager {
public int startOpNoThrow(int op, int uid, @NonNull String packageName,
boolean startIfModeDefault, @Nullable String featureId, @Nullable String message) {
try {
collectNoteOpCallsForValidation(op);
int collectionMode = getNotedOpCollectionMode(uid, packageName, op);
if (collectionMode == COLLECT_ASYNC) {
if (message == null) {
@@ -8492,4 +8504,24 @@ public class AppOpsManager {
public static int leftCircularDistance(int from, int to, int 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
}
}
}
}

View File

@@ -103,4 +103,6 @@ interface IAppOpsService {
int checkOperationRaw(int code, int uid, String packageName);
void reloadNonHistoricalState();
void collectNoteOpCallsForValidation(String stackTrace, int op, String packageName, long version);
}

View File

@@ -138,6 +138,7 @@ import android.util.TimeUtils;
import android.util.Xml;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.Immutable;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.app.IAppOpsActiveCallback;
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.LockGuard;
import com.android.server.SystemServerInitThreadPool;
import com.android.server.SystemServiceManager;
import com.android.server.pm.PackageList;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import libcore.util.EmptyArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException;
import org.xmlpull.v1.XmlSerializer;
@@ -169,6 +173,7 @@ import java.io.FileDescriptor;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.nio.charset.StandardCharsets;
@@ -184,6 +189,7 @@ import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Scanner;
import java.util.concurrent.ThreadLocalRandom;
import java.util.function.Consumer;
@@ -191,6 +197,11 @@ public class AppOpsService extends IAppOpsService.Stub {
static final String TAG = "AppOps";
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;
/** Increment by one every time and add the corresponding upgrade logic in
* {@link #upgradeLocked(int)} below. The first version was 1 */
@@ -241,6 +252,7 @@ public class AppOpsService extends IAppOpsService.Stub {
final Context mContext;
final AtomicFile mFile;
private final @Nullable File mNoteOpCallerStacktracesFile;
final Handler mHandler;
/** 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>>
mUnforwardedAsyncNotedOps = new ArrayMap<>();
boolean mWriteNoteOpsScheduled;
boolean mWriteScheduled;
boolean mFastWriteScheduled;
final Runnable mWriteRunner = new Runnable() {
@@ -1397,11 +1411,42 @@ public class AppOpsService extends IAppOpsService.Stub {
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) {
mContext = context;
LockGuard.installLock(this, LockGuard.INDEX_APP_OPS);
mFile = new AtomicFile(storagePath, "appops");
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED) {
mNoteOpCallerStacktracesFile = new File(SystemServiceManager.ensureSystemDir(),
"noteOpStackTraces.json");
readNoteOpCallerStackTraces();
} else {
mNoteOpCallerStacktracesFile = null;
}
mHandler = handler;
mConstants = new Constants(mHandler);
readState();
@@ -1802,6 +1847,9 @@ public class AppOpsService extends IAppOpsService.Stub {
if (doWrite) {
writeState();
}
if (AppOpsManager.NOTE_OP_COLLECTION_ENABLED && mWriteNoteOpsScheduled) {
writeNoteOps();
}
}
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);
}
}
/**
* 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);
}
}
}
}