diff options
4 files changed, 180 insertions, 4 deletions
diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java index 7165e600ca2f..5f4e47147109 100644 --- a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistService.java @@ -23,8 +23,10 @@ import android.net.INetdEventCallback; import android.net.metrics.IpConnectivityLog; import android.os.Binder; import android.os.Process; +import android.os.ResultReceiver; import android.os.RemoteException; import android.os.ServiceManager; +import android.os.ShellCallback; import android.os.SystemProperties; import android.provider.Settings; import android.text.TextUtils; @@ -80,6 +82,7 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { return; } try { + mService.init(); mService.initIpConnectivityMetrics(); mService.startWatchlistLogging(); } catch (RemoteException e) { @@ -127,6 +130,10 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { mIpConnectivityMetrics = ipConnectivityMetrics; } + private void init() { + mConfig.removeTestModeConfig(); + } + private void initIpConnectivityMetrics() { mIpConnectivityMetrics = (IIpConnectivityMetrics) IIpConnectivityMetrics.Stub.asInterface( ServiceManager.getService(IpConnectivityLog.SERVICE_NAME)); @@ -151,6 +158,22 @@ public class NetworkWatchlistService extends INetworkWatchlistManager.Stub { } }; + private boolean isCallerShell() { + final int callingUid = Binder.getCallingUid(); + return callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID; + } + + @Override + public void onShellCommand(FileDescriptor in, FileDescriptor out, FileDescriptor err, + String[] args, ShellCallback callback, ResultReceiver resultReceiver) { + if (!isCallerShell()) { + Slog.w(TAG, "Only shell is allowed to call network watchlist shell commands"); + return; + } + (new NetworkWatchlistShellCommand(mContext)).exec(this, in, out, err, args, callback, + resultReceiver); + } + @VisibleForTesting protected boolean startWatchlistLoggingImpl() throws RemoteException { if (DEBUG) { diff --git a/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java new file mode 100644 index 000000000000..9533823df808 --- /dev/null +++ b/services/core/java/com/android/server/net/watchlist/NetworkWatchlistShellCommand.java @@ -0,0 +1,94 @@ +/* + * Copyright (C) 2018 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.net.watchlist; + +import android.content.Context; +import android.content.Intent; +import android.net.NetworkWatchlistManager; +import android.os.ParcelFileDescriptor; +import android.os.RemoteException; +import android.os.ShellCommand; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.PrintWriter; + +/** + * Network watchlist shell commands class, to provide a way to set temporary watchlist config for + * testing in shell, so CTS / GTS can use it to verify if watchlist feature is working properly. + */ +class NetworkWatchlistShellCommand extends ShellCommand { + + final NetworkWatchlistManager mNetworkWatchlistManager; + + NetworkWatchlistShellCommand(Context context) { + mNetworkWatchlistManager = new NetworkWatchlistManager(context); + } + + @Override + public int onCommand(String cmd) { + if (cmd == null) { + return handleDefaultCommands(cmd); + } + + final PrintWriter pw = getOutPrintWriter(); + try { + switch(cmd) { + case "set-test-config": + return runSetTestConfig(); + default: + return handleDefaultCommands(cmd); + } + } catch (RemoteException e) { + pw.println("Remote exception: " + e); + } + return -1; + } + + /** + * Method to get fd from input xml path, and set it as temporary watchlist config. + */ + private int runSetTestConfig() throws RemoteException { + final PrintWriter pw = getOutPrintWriter(); + try { + final String configXmlPath = getNextArgRequired(); + final ParcelFileDescriptor pfd = openFileForSystem(configXmlPath, "r"); + if (pfd != null) { + final InputStream fileStream = new FileInputStream(pfd.getFileDescriptor()); + WatchlistConfig.getInstance().setTestMode(fileStream); + } + pw.println("Success!"); + } catch (RuntimeException | IOException ex) { + pw.println("Error: " + ex.toString()); + return -1; + } + 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 , ""); + } +} diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java index 7387ad4f90ff..2714d5e5f605 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistConfig.java @@ -16,6 +16,7 @@ package com.android.server.net.watchlist; +import android.os.FileUtils; import android.util.AtomicFile; import android.util.Log; import android.util.Slog; @@ -32,6 +33,7 @@ import java.io.File; import java.io.FileDescriptor; import java.io.FileInputStream; import java.io.IOException; +import java.io.InputStream; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; import java.security.MessageDigest; @@ -50,6 +52,8 @@ class WatchlistConfig { // Watchlist config that pushed by ConfigUpdater. private static final String NETWORK_WATCHLIST_DB_PATH = "/data/misc/network_watchlist/network_watchlist.xml"; + private static final String NETWORK_WATCHLIST_DB_FOR_TEST_PATH = + "/data/misc/network_watchlist/network_watchlist_for_test.xml"; // Hash for null / unknown config, a 32 byte array filled with content 0x00 private static final byte[] UNKNOWN_CONFIG_HASH = new byte[32]; @@ -80,7 +84,7 @@ class WatchlistConfig { private boolean mIsSecureConfig = true; private final static WatchlistConfig sInstance = new WatchlistConfig(); - private final File mXmlFile; + private File mXmlFile; private volatile CrcShaDigests mDomainDigests; private volatile CrcShaDigests mIpDigests; @@ -232,7 +236,38 @@ class WatchlistConfig { return UNKNOWN_CONFIG_HASH; } + /** + * This method will copy temporary test config and temporary override network watchlist config + * in memory. When device is rebooted, temporary test config will be removed, and system will + * use back the original watchlist config. + * Also, as temporary network watchlist config is not secure, we will mark it as insecure + * config and will be applied to testOnly applications only. + */ + public void setTestMode(InputStream testConfigInputStream) throws IOException { + Log.i(TAG, "Setting watchlist testing config"); + // Copy test config + FileUtils.copyToFileOrThrow(testConfigInputStream, + new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH)); + // Mark config as insecure, so it will be applied to testOnly applications only + mIsSecureConfig = false; + // Reload watchlist config using test config file + mXmlFile = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH); + reloadConfig(); + } + + public void removeTestModeConfig() { + try { + final File f = new File(NETWORK_WATCHLIST_DB_FOR_TEST_PATH); + if (f.exists()) { + f.delete(); + } + } catch (Exception e) { + Log.e(TAG, "Unable to delete test config"); + } + } + public void dump(FileDescriptor fd, PrintWriter pw, String[] args) { + pw.println("Watchlist config hash: " + HexDump.toHexString(getWatchlistConfigHash())); pw.println("Domain CRC32 digest list:"); if (mDomainDigests != null) { mDomainDigests.crc32Digests.dump(fd, pw, args); diff --git a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java index 3b6d59e178f2..c4de4ac1b7dc 100644 --- a/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java +++ b/services/core/java/com/android/server/net/watchlist/WatchlistLoggingHandler.java @@ -118,6 +118,25 @@ class WatchlistLoggingHandler extends Handler { } /** + * Return if a given package has testOnly is true. + */ + private boolean isPackageTestOnly(int uid) { + final ApplicationInfo ai; + try { + final String[] packageNames = mPm.getPackagesForUid(uid); + if (packageNames == null || packageNames.length == 0) { + Slog.e(TAG, "Couldn't find package: " + packageNames); + return false; + } + ai = mPm.getApplicationInfo(packageNames[0],0); + } catch (NameNotFoundException e) { + // Should not happen. + return false; + } + return (ai.flags & ApplicationInfo.FLAG_TEST_ONLY) != 0; + } + + /** * Report network watchlist records if we collected enough data. */ public void reportWatchlistIfNecessary() { @@ -146,16 +165,21 @@ class WatchlistLoggingHandler extends Handler { } final String cncDomain = searchAllSubDomainsInWatchlist(hostname); if (cncDomain != null) { - insertRecord(getDigestFromUid(uid), cncDomain, timestamp); + insertRecord(uid, cncDomain, timestamp); } else { final String cncIp = searchIpInWatchlist(ipAddresses); if (cncIp != null) { - insertRecord(getDigestFromUid(uid), cncIp, timestamp); + insertRecord(uid, cncIp, timestamp); } } } - private boolean insertRecord(byte[] digest, String cncHost, long timestamp) { + private boolean insertRecord(int uid, String cncHost, long timestamp) { + if (!mConfig.isConfigSecure() && !isPackageTestOnly(uid)) { + // Skip package if config is not secure and package is not TestOnly app. + return true; + } + final byte[] digest = getDigestFromUid(uid); final boolean result = mDbHelper.insertNewRecord(digest, cncHost, timestamp); tryAggregateRecords(); return result; |