Merge "Add adb command to force generate network watchlist report for testing" into pi-dev

This commit is contained in:
Ricky Wai
2018-03-29 00:04:26 +00:00
committed by Android (Google) Code Review
6 changed files with 150 additions and 65 deletions

View File

@@ -19,6 +19,7 @@ package android.privacy.internal.longitudinalreporting;
import android.privacy.DifferentialPrivacyEncoder;
import android.privacy.internal.rappor.RapporConfig;
import android.privacy.internal.rappor.RapporEncoder;
import android.util.Log;
import com.android.internal.annotations.VisibleForTesting;
@@ -48,6 +49,9 @@ import com.android.internal.annotations.VisibleForTesting;
*/
public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder {
private static final String TAG = "LongitudinalEncoder";
private static final boolean DEBUG = false;
// Suffix that will be added to Rappor's encoder id. There's a (relatively) small risk some
// other Rappor encoder may re-use the same encoder id.
private static final String PRR1_ENCODER_ID = "prr1_encoder_id";
@@ -121,11 +125,18 @@ public class LongitudinalReportingEncoder implements DifferentialPrivacyEncoder
@Override
public byte[] encodeBoolean(boolean original) {
if (DEBUG) {
Log.d(TAG, "encodeBoolean, encoderId:" + mConfig.getEncoderId() + ", original: "
+ original);
}
if (mFakeValue != null) {
// Use the fake value generated in PRR.
original = mFakeValue.booleanValue();
if (DEBUG) Log.d(TAG, "Use fake value: " + original);
}
return mIRREncoder.encodeBoolean(original);
byte[] result = mIRREncoder.encodeBoolean(original);
if (DEBUG) Log.d(TAG, "result: " + ((result[0] & 0x1) != 0));
return result;
}
@Override

View File

@@ -171,7 +171,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
Slog.w(TAG, "Only shell is allowed to call network watchlist shell commands");
return;
}
(new NetworkWatchlistShellCommand(mContext)).exec(this, in, out, err, args, callback,
(new NetworkWatchlistShellCommand(this, mContext)).exec(this, in, out, err, args, callback,
resultReceiver);
}
@@ -262,6 +262,21 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub {
mNetworkWatchlistHandler.reportWatchlistIfNecessary();
}
/**
* Force generate watchlist report for testing.
*
* @param lastReportTime Watchlist report will cotain all records before this time.
* @return True if operation success.
*/
public boolean forceReportWatchlistForTest(long lastReportTime) {
if (mConfig.isConfigSecure()) {
// Should not force generate report under production config.
return false;
}
mNetworkWatchlistHandler.forceReportWatchlistForTest(lastReportTime);
return true;
}
@Override
protected void dump(FileDescriptor fd, PrintWriter pw, String[] args) {
if (!DumpUtils.checkDumpPermission(mContext, TAG, pw)) return;

View File

@@ -19,9 +19,11 @@ package com.android.server.net.watchlist;
import android.content.Context;
import android.content.Intent;
import android.net.NetworkWatchlistManager;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.os.ShellCommand;
import android.provider.Settings;
import java.io.FileInputStream;
import java.io.IOException;
@@ -34,10 +36,12 @@ import java.io.PrintWriter;
*/
class NetworkWatchlistShellCommand extends ShellCommand {
final NetworkWatchlistManager mNetworkWatchlistManager;
final Context mContext;
final NetworkWatchlistService mService;
NetworkWatchlistShellCommand(Context context) {
mNetworkWatchlistManager = new NetworkWatchlistManager(context);
NetworkWatchlistShellCommand(NetworkWatchlistService service, Context context) {
mContext = context;
mService = service;
}
@Override
@@ -51,11 +55,13 @@ class NetworkWatchlistShellCommand extends ShellCommand {
switch(cmd) {
case "set-test-config":
return runSetTestConfig();
case "force-generate-report":
return runForceGenerateReport();
default:
return handleDefaultCommands(cmd);
}
} catch (RemoteException e) {
pw.println("Remote exception: " + e);
} catch (Exception e) {
pw.println("Exception: " + e);
}
return -1;
}
@@ -73,22 +79,44 @@ class NetworkWatchlistShellCommand extends ShellCommand {
WatchlistConfig.getInstance().setTestMode(fileStream);
}
pw.println("Success!");
} catch (RuntimeException | IOException ex) {
} catch (Exception ex) {
pw.println("Error: " + ex.toString());
return -1;
}
return 0;
}
private int runForceGenerateReport() throws RemoteException {
final PrintWriter pw = getOutPrintWriter();
final long ident = Binder.clearCallingIdentity();
try {
// Reset last report time
if (!WatchlistConfig.getInstance().isConfigSecure()) {
pw.println("Error: Cannot force generate report under production config");
return -1;
}
Settings.Global.putLong(mContext.getContentResolver(),
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L);
mService.forceReportWatchlistForTest(System.currentTimeMillis());
pw.println("Success!");
} catch (Exception ex) {
pw.println("Error: " + ex);
return -1;
} finally {
Binder.restoreCallingIdentity(ident);
}
return 0;
}
@Override
public void onHelp() {
final PrintWriter pw = getOutPrintWriter();
pw.println("Network watchlist manager commands:");
pw.println(" help");
pw.println(" Print this help text.");
pw.println("");
pw.println(" set-test-config your_watchlist_config.xml");
pw.println();
Intent.printIntentArgsHelp(pw , "");
pw.println(" Set network watchlist test config file.");
pw.println(" force-generate-report");
pw.println(" Force generate watchlist test report.");
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.net.watchlist;
import android.privacy.DifferentialPrivacyEncoder;
import android.privacy.internal.longitudinalreporting.LongitudinalReportingConfig;
import android.privacy.internal.longitudinalreporting.LongitudinalReportingEncoder;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
@@ -32,6 +33,7 @@ import java.util.Map;
class PrivacyUtils {
private static final String TAG = "PrivacyUtils";
private static final boolean DEBUG = NetworkWatchlistService.DEBUG;
/**
* Parameters used for encoding watchlist reports.
@@ -84,6 +86,7 @@ class PrivacyUtils {
@VisibleForTesting
static Map<String, Boolean> createDpEncodedReportMap(boolean isSecure, byte[] userSecret,
List<String> appDigestList, WatchlistReportDbHelper.AggregatedResult aggregatedResult) {
if (DEBUG) Slog.i(TAG, "createDpEncodedReportMap start");
final int appDigestListSize = appDigestList.size();
final HashMap<String, Boolean> resultMap = new HashMap<>(appDigestListSize);
for (int i = 0; i < appDigestListSize; i++) {
@@ -93,6 +96,7 @@ class PrivacyUtils {
? createSecureDPEncoder(userSecret, appDigest)
: createInsecureDPEncoderForTest(appDigest);
final boolean visitedWatchlist = aggregatedResult.appDigestList.contains(appDigest);
if (DEBUG) Slog.i(TAG, appDigest + ": " + visitedWatchlist);
// Get the least significant bit of first byte, and set result to True if it is 1
boolean encodedVisitedWatchlist = ((int) encoder.encodeBoolean(visitedWatchlist)[0]
& 0x1) == 0x1;

View File

@@ -43,6 +43,7 @@ import java.io.File;
import java.io.IOException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
@@ -61,6 +62,8 @@ class WatchlistLoggingHandler extends Handler {
static final int LOG_WATCHLIST_EVENT_MSG = 1;
@VisibleForTesting
static final int REPORT_RECORDS_IF_NECESSARY_MSG = 2;
@VisibleForTesting
static final int FORCE_REPORT_RECORDS_NOW_FOR_TEST_MSG = 3;
private static final long ONE_DAY_MS = TimeUnit.DAYS.toMillis(1);
private static final String DROPBOX_TAG = "network_watchlist_report";
@@ -110,7 +113,15 @@ class WatchlistLoggingHandler extends Handler {
break;
}
case REPORT_RECORDS_IF_NECESSARY_MSG:
tryAggregateRecords();
tryAggregateRecords(getLastMidnightTime());
break;
case FORCE_REPORT_RECORDS_NOW_FOR_TEST_MSG:
if (msg.obj instanceof Long) {
long lastRecordTime = (Long) msg.obj;
tryAggregateRecords(lastRecordTime);
} else {
Slog.e(TAG, "Msg.obj needs to be a Long object.");
}
break;
default: {
Slog.d(TAG, "WatchlistLoggingHandler received an unknown of message.");
@@ -146,6 +157,12 @@ class WatchlistLoggingHandler extends Handler {
sendMessage(msg);
}
public void forceReportWatchlistForTest(long lastReportTime) {
final Message msg = obtainMessage(FORCE_REPORT_RECORDS_NOW_FOR_TEST_MSG);
msg.obj = lastReportTime;
sendMessage(msg);
}
/**
* Insert network traffic event to watchlist async queue processor.
*/
@@ -177,8 +194,14 @@ class WatchlistLoggingHandler extends Handler {
}
private boolean insertRecord(int uid, String cncHost, long timestamp) {
if (DEBUG) {
Slog.i(TAG, "trying to insert record with host: " + cncHost + ", uid: " + uid);
}
if (!mConfig.isConfigSecure() && !isPackageTestOnly(uid)) {
// Skip package if config is not secure and package is not TestOnly app.
if (DEBUG) {
Slog.i(TAG, "uid: " + uid + " is not test only package");
}
return true;
}
final byte[] digest = getDigestFromUid(uid);
@@ -187,50 +210,56 @@ class WatchlistLoggingHandler extends Handler {
return false;
}
final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp);
tryAggregateRecords();
return result;
}
private boolean shouldReportNetworkWatchlist() {
private boolean shouldReportNetworkWatchlist(long lastRecordTime) {
final long lastReportTime = Settings.Global.getLong(mResolver,
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME, 0L);
final long currentTimestamp = System.currentTimeMillis();
if (currentTimestamp < lastReportTime) {
if (lastRecordTime < lastReportTime) {
Slog.i(TAG, "Last report time is larger than current time, reset report");
mDbHelper.cleanup();
mDbHelper.cleanup(lastReportTime);
return false;
}
return currentTimestamp >= lastReportTime + ONE_DAY_MS;
return lastRecordTime >= lastReportTime + ONE_DAY_MS;
}
private void tryAggregateRecords() {
// Check if it's necessary to generate watchlist report now.
if (!shouldReportNetworkWatchlist()) {
Slog.i(TAG, "No need to aggregate record yet.");
return;
}
Slog.i(TAG, "Start aggregating watchlist records.");
if (mDropBoxManager != null && mDropBoxManager.isTagEnabled(DROPBOX_TAG)) {
Settings.Global.putLong(mResolver,
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
System.currentTimeMillis());
final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
mDbHelper.getAggregatedRecords();
if (aggregatedResult == null) {
Slog.i(TAG, "Cannot get result from database");
private void tryAggregateRecords(long lastRecordTime) {
long startTime = System.currentTimeMillis();
try {
// Check if it's necessary to generate watchlist report now.
if (!shouldReportNetworkWatchlist(lastRecordTime)) {
Slog.i(TAG, "No need to aggregate record yet.");
return;
}
// Get all digests for watchlist report, it should include all installed
// application digests and previously recorded app digests.
final List<String> digestsForReport = getAllDigestsForReport(aggregatedResult);
final byte[] secretKey = mSettings.getPrivacySecretKey();
final byte[] encodedResult = ReportEncoder.encodeWatchlistReport(mConfig,
secretKey, digestsForReport, aggregatedResult);
if (encodedResult != null) {
addEncodedReportToDropBox(encodedResult);
Slog.i(TAG, "Start aggregating watchlist records.");
if (mDropBoxManager != null && mDropBoxManager.isTagEnabled(DROPBOX_TAG)) {
Settings.Global.putLong(mResolver,
Settings.Global.NETWORK_WATCHLIST_LAST_REPORT_TIME,
lastRecordTime);
final WatchlistReportDbHelper.AggregatedResult aggregatedResult =
mDbHelper.getAggregatedRecords(lastRecordTime);
if (aggregatedResult == null) {
Slog.i(TAG, "Cannot get result from database");
return;
}
// Get all digests for watchlist report, it should include all installed
// application digests and previously recorded app digests.
final List<String> digestsForReport = getAllDigestsForReport(aggregatedResult);
final byte[] secretKey = mSettings.getPrivacySecretKey();
final byte[] encodedResult = ReportEncoder.encodeWatchlistReport(mConfig,
secretKey, digestsForReport, aggregatedResult);
if (encodedResult != null) {
addEncodedReportToDropBox(encodedResult);
}
} else {
Slog.w(TAG, "Network Watchlist dropbox tag is not enabled");
}
mDbHelper.cleanup(lastRecordTime);
} finally {
long endTime = System.currentTimeMillis();
Slog.i(TAG, "Milliseconds spent on tryAggregateRecords(): " + (endTime - startTime));
}
mDbHelper.cleanup();
}
/**
@@ -379,4 +408,19 @@ class WatchlistLoggingHandler extends Handler {
}
return subDomainList.toArray(new String[0]);
}
static long getLastMidnightTime() {
return getMidnightTimestamp(0);
}
static long getMidnightTimestamp(int daysBefore) {
java.util.Calendar date = new GregorianCalendar();
// reset hour, minutes, seconds and millis
date.set(java.util.Calendar.HOUR_OF_DAY, 0);
date.set(java.util.Calendar.MINUTE, 0);
date.set(java.util.Calendar.SECOND, 0);
date.set(java.util.Calendar.MILLISECOND, 0);
date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore);
return date.getTimeInMillis();
}
}

View File

@@ -141,11 +141,10 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper {
}
/**
* Aggregate all records before most recent local midnight in database, and return a
* Aggregate all records in database before input timestamp, and return a
* rappor encoded result.
*/
public AggregatedResult getAggregatedRecords() {
final long lastMidnightTime = getLastMidnightTime();
public AggregatedResult getAggregatedRecords(long untilTimestamp) {
final String selectStatement = WhiteListReportContract.TIMESTAMP + " < ?";
final SQLiteDatabase db = getReadableDatabase();
@@ -153,7 +152,7 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper {
try {
c = db.query(true /* distinct */,
WhiteListReportContract.TABLE, DIGEST_DOMAIN_PROJECTION, selectStatement,
new String[]{"" + lastMidnightTime}, null, null,
new String[]{Long.toString(untilTimestamp)}, null, null,
null, null);
if (c == null) {
return null;
@@ -181,29 +180,13 @@ class WatchlistReportDbHelper extends SQLiteOpenHelper {
}
/**
* Remove all the records before most recent local midnight.
* Remove all the records before input timestamp.
*
* @return True if success.
*/
public boolean cleanup() {
public boolean cleanup(long untilTimestamp) {
final SQLiteDatabase db = getWritableDatabase();
final long midnightTime = getLastMidnightTime();
final String clause = WhiteListReportContract.TIMESTAMP + "< " + midnightTime;
final String clause = WhiteListReportContract.TIMESTAMP + "< " + untilTimestamp;
return db.delete(WhiteListReportContract.TABLE, clause, null) != 0;
}
static long getLastMidnightTime() {
return getMidnightTimestamp(0);
}
static long getMidnightTimestamp(int daysBefore) {
java.util.Calendar date = new GregorianCalendar();
// reset hour, minutes, seconds and millis
date.set(java.util.Calendar.HOUR_OF_DAY, 0);
date.set(java.util.Calendar.MINUTE, 0);
date.set(java.util.Calendar.SECOND, 0);
date.set(java.util.Calendar.MILLISECOND, 0);
date.add(java.util.Calendar.DAY_OF_MONTH, -daysBefore);
return date.getTimeInMillis();
}
}