Merge "Relax permissions around NetworkStatsManager APIs."
This commit is contained in:
committed by
Android (Google) Code Review
commit
40e2ce37d3
@@ -21,6 +21,7 @@ import android.content.Context;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkIdentity;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.Build;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Log;
|
||||
|
||||
@@ -29,10 +30,9 @@ import android.util.Log;
|
||||
* discrete bins of time called 'Buckets'. See {@link NetworkStats.Bucket} for details.
|
||||
* <p />
|
||||
* Queries can define a time interval in the form of start and end timestamps (Long.MIN_VALUE and
|
||||
* Long.MAX_VALUE can be used to simulate open ended intervals). All queries (except
|
||||
* {@link #querySummaryForDevice}) collect only network usage of apps belonging to the same user
|
||||
* as the client. In addition tethering usage, usage by removed users and apps, and usage by system
|
||||
* is also included in the results.
|
||||
* Long.MAX_VALUE can be used to simulate open ended intervals). By default, apps can only obtain
|
||||
* data about themselves. See the below note for special cases in which apps can obtain data about
|
||||
* other applications.
|
||||
* <h3>
|
||||
* Summary queries
|
||||
* </h3>
|
||||
@@ -51,13 +51,20 @@ import android.util.Log;
|
||||
* multiple buckets for a particular key but all Bucket's state is going to be
|
||||
* {@link NetworkStats.Bucket#STATE_ALL}.
|
||||
* <p />
|
||||
* <b>NOTE:</b> This API requires the permission
|
||||
* <b>NOTE:</b> Accessing stats for apps other than the calling app requires the permission
|
||||
* {@link android.Manifest.permission#PACKAGE_USAGE_STATS}, which is a system-level permission and
|
||||
* will not be granted to third-party apps. However, declaring the permission implies intention to
|
||||
* use the API and the user of the device can grant permission through the Settings application.
|
||||
* Profile owner apps are automatically granted permission to query data on the profile they manage
|
||||
* (that is, for any query except {@link #querySummaryForDevice}). Device owner apps likewise get
|
||||
* access to usage data of the primary user.
|
||||
* (that is, for any query except {@link #querySummaryForDevice}). Device owner apps and carrier-
|
||||
* privileged apps likewise get access to usage data for all users on the device.
|
||||
* <p />
|
||||
* In addition to tethering usage, usage by removed users and apps, and usage by the system
|
||||
* is also included in the results for callers with one of these higher levels of access.
|
||||
* <p />
|
||||
* <b>NOTE:</b> Prior to API level {@value Build.VERSION_CODES#N}, all calls to these APIs required
|
||||
* the above permission, even to access an app's own data usage, and carrier-privileged apps were
|
||||
* not included.
|
||||
*/
|
||||
public class NetworkStatsManager {
|
||||
private final static String TAG = "NetworkStatsManager";
|
||||
|
||||
@@ -0,0 +1,166 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import static android.Manifest.permission.READ_NETWORK_USAGE_HISTORY;
|
||||
import static android.net.TrafficStats.UID_REMOVED;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
|
||||
import android.Manifest;
|
||||
import android.annotation.IntDef;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.admin.DeviceAdminInfo;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.os.UserHandle;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/** Utility methods for controlling access to network stats APIs. */
|
||||
public final class NetworkStatsAccess {
|
||||
private NetworkStatsAccess() {}
|
||||
|
||||
/**
|
||||
* Represents an access level for the network usage history and statistics APIs.
|
||||
*
|
||||
* <p>Access levels are in increasing order; that is, it is reasonable to check access by
|
||||
* verifying that the caller's access level is at least the minimum required level.
|
||||
*/
|
||||
@IntDef({
|
||||
Level.DEFAULT,
|
||||
Level.USER,
|
||||
Level.DEVICE,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
public @interface Level {
|
||||
/**
|
||||
* Default, unprivileged access level.
|
||||
*
|
||||
* <p>Can only access usage for one's own UID.
|
||||
*
|
||||
* <p>Every app will have at least this access level.
|
||||
*/
|
||||
int DEFAULT = 0;
|
||||
|
||||
/**
|
||||
* Access level for apps which can access usage for any app running in the same user.
|
||||
*
|
||||
* <p>Granted to:
|
||||
* <ul>
|
||||
* <li>Apps with the PACKAGE_USAGE_STATS permission granted. Note that this is an AppOps bit
|
||||
* so it is not necessarily sufficient to declare this in the manifest.
|
||||
* <li>Apps with the (signature/privileged) READ_NETWORK_USAGE_HISTORY permission.
|
||||
* <li>Profile owners.
|
||||
* </ul>
|
||||
*/
|
||||
int USER = 1;
|
||||
|
||||
/**
|
||||
* Access level for apps which can access usage for any app on the device, including apps
|
||||
* running on other users/profiles.
|
||||
*
|
||||
* <p>Granted to:
|
||||
* <ul>
|
||||
* <li>Device owners.
|
||||
* <li>Carrier-privileged applications.
|
||||
* <li>The system UID.
|
||||
* </ul>
|
||||
*/
|
||||
int DEVICE = 2;
|
||||
}
|
||||
|
||||
/** Returns the {@link NetworkStatsAccess.Level} for the given caller. */
|
||||
public static @NetworkStatsAccess.Level int checkAccessLevel(
|
||||
Context context, int callingUid, String callingPackage) {
|
||||
final DevicePolicyManagerInternal dpmi = LocalServices.getService(
|
||||
DevicePolicyManagerInternal.class);
|
||||
final TelephonyManager tm = (TelephonyManager)
|
||||
context.getSystemService(Context.TELEPHONY_SERVICE);
|
||||
boolean hasCarrierPrivileges = tm != null &&
|
||||
tm.checkCarrierPrivilegesForPackage(callingPackage) ==
|
||||
TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS;
|
||||
boolean isDeviceOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
|
||||
DeviceAdminInfo.USES_POLICY_DEVICE_OWNER);
|
||||
if (hasCarrierPrivileges || isDeviceOwner
|
||||
|| UserHandle.getAppId(callingUid) == android.os.Process.SYSTEM_UID) {
|
||||
// Carrier-privileged apps and device owners, and the system can access data usage for
|
||||
// all apps on the device.
|
||||
return NetworkStatsAccess.Level.DEVICE;
|
||||
}
|
||||
|
||||
boolean isProfileOwner = dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
|
||||
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER);
|
||||
if (hasAppOpsPermission(context, callingUid, callingPackage) || isProfileOwner
|
||||
|| context.checkCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY) ==
|
||||
PackageManager.PERMISSION_GRANTED) {
|
||||
// Apps with the AppOps permission, profile owners, and apps with the privileged
|
||||
// permission can access data usage for all apps in this user/profile.
|
||||
return NetworkStatsAccess.Level.USER;
|
||||
}
|
||||
|
||||
// Everyone else gets default access (only to their own UID).
|
||||
return NetworkStatsAccess.Level.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the given caller should be able to access the given UID when the caller has
|
||||
* the given {@link NetworkStatsAccess.Level}.
|
||||
*/
|
||||
public static boolean isAccessibleToUser(int uid, int callerUid,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
switch (accessLevel) {
|
||||
case NetworkStatsAccess.Level.DEVICE:
|
||||
// Device-level access - can access usage for any uid.
|
||||
return true;
|
||||
case NetworkStatsAccess.Level.USER:
|
||||
// User-level access - can access usage for any app running in the same user, along
|
||||
// with some special uids (system, removed, or tethering).
|
||||
return uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED
|
||||
|| uid == UID_TETHERING
|
||||
|| UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
|
||||
case NetworkStatsAccess.Level.DEFAULT:
|
||||
default:
|
||||
// Default access level - can only access one's own usage.
|
||||
return uid == callerUid;
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean hasAppOpsPermission(
|
||||
Context context, int callingUid, String callingPackage) {
|
||||
if (callingPackage != null) {
|
||||
AppOpsManager appOps = (AppOpsManager) context.getSystemService(
|
||||
Context.APP_OPS_SERVICE);
|
||||
|
||||
final int mode = appOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
|
||||
callingUid, callingPackage);
|
||||
if (mode == AppOpsManager.MODE_DEFAULT) {
|
||||
// The default behavior here is to check if PackageManager has given the app
|
||||
// permission.
|
||||
final int permissionCheck = context.checkCallingPermission(
|
||||
Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
return permissionCheck == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
return (mode == AppOpsManager.MODE_ALLOWED);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -22,24 +22,18 @@ import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
import static android.net.TrafficStats.UID_REMOVED;
|
||||
import static android.net.TrafficStats.UID_TETHERING;
|
||||
import static android.text.format.DateUtils.SECOND_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.WEEK_IN_MILLIS;
|
||||
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.NetworkIdentity;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkStatsHistory;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.net.TrafficStats;
|
||||
import android.os.Binder;
|
||||
import android.os.UserHandle;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.AtomicFile;
|
||||
import android.util.IntArray;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.FileRotator;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
@@ -47,6 +41,8 @@ import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.google.android.collect.Lists;
|
||||
import com.google.android.collect.Maps;
|
||||
|
||||
import libcore.io.IoUtils;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.DataInputStream;
|
||||
import java.io.DataOutputStream;
|
||||
@@ -136,12 +132,12 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
return mStartMillis == Long.MAX_VALUE && mEndMillis == Long.MIN_VALUE;
|
||||
}
|
||||
|
||||
public int[] getRelevantUids() {
|
||||
public int[] getRelevantUids(@NetworkStatsAccess.Level int accessLevel) {
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
IntArray uids = new IntArray();
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
if (isAccessibleToUser(key.uid, callerUid)) {
|
||||
if (NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)) {
|
||||
int j = uids.binarySearch(key.uid);
|
||||
|
||||
if (j < 0) {
|
||||
@@ -158,8 +154,10 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
* the requested parameters.
|
||||
*/
|
||||
public NetworkStatsHistory getHistory(
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields) {
|
||||
return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE);
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
return getHistory(template, uid, set, tag, fields, Long.MIN_VALUE, Long.MAX_VALUE,
|
||||
accessLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -167,9 +165,10 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
* the requested parameters.
|
||||
*/
|
||||
public NetworkStatsHistory getHistory(
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end) {
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields, long start, long end,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
if (!isAccessibleToUser(uid, callerUid)) {
|
||||
if (!NetworkStatsAccess.isAccessibleToUser(uid, callerUid, accessLevel)) {
|
||||
throw new SecurityException("Network stats history of uid " + uid
|
||||
+ " is forbidden for caller " + callerUid);
|
||||
}
|
||||
@@ -195,7 +194,8 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
* Summarize all {@link NetworkStatsHistory} in this collection which match
|
||||
* the requested parameters.
|
||||
*/
|
||||
public NetworkStats getSummary(NetworkTemplate template, long start, long end) {
|
||||
public NetworkStats getSummary(NetworkTemplate template, long start, long end,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
final long now = System.currentTimeMillis();
|
||||
|
||||
final NetworkStats stats = new NetworkStats(end - start, 24);
|
||||
@@ -208,7 +208,8 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
final int callerUid = Binder.getCallingUid();
|
||||
for (int i = 0; i < mStats.size(); i++) {
|
||||
final Key key = mStats.keyAt(i);
|
||||
if (templateMatches(template, key.ident) && isAccessibleToUser(key.uid, callerUid)
|
||||
if (templateMatches(template, key.ident)
|
||||
&& NetworkStatsAccess.isAccessibleToUser(key.uid, callerUid, accessLevel)
|
||||
&& key.set < NetworkStats.SET_DEBUG_START) {
|
||||
final NetworkStatsHistory value = mStats.valueAt(i);
|
||||
historyEntry = value.getValues(start, end, now, historyEntry);
|
||||
@@ -570,12 +571,6 @@ public class NetworkStatsCollection implements FileRotator.Reader {
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isAccessibleToUser(int uid, int callerUid) {
|
||||
return UserHandle.getAppId(callerUid) == android.os.Process.SYSTEM_UID ||
|
||||
uid == android.os.Process.SYSTEM_UID || uid == UID_REMOVED || uid == UID_TETHERING
|
||||
|| UserHandle.getUserId(uid) == UserHandle.getUserId(callerUid);
|
||||
}
|
||||
|
||||
/**
|
||||
* Test if given {@link NetworkTemplate} matches any {@link NetworkIdentity}
|
||||
* in the given {@link NetworkIdentitySet}.
|
||||
|
||||
@@ -116,7 +116,8 @@ public class NetworkStatsRecorder {
|
||||
}
|
||||
|
||||
public NetworkStats.Entry getTotalSinceBootLocked(NetworkTemplate template) {
|
||||
return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
|
||||
return mSinceBoot.getSummary(template, Long.MIN_VALUE, Long.MAX_VALUE,
|
||||
NetworkStatsAccess.Level.DEVICE).getTotal(null);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -62,13 +62,9 @@ import static com.android.server.NetworkManagementService.LIMIT_GLOBAL_ALERT;
|
||||
import static com.android.server.NetworkManagementSocketTagger.resetKernelUidStats;
|
||||
import static com.android.server.NetworkManagementSocketTagger.setKernelCounterSet;
|
||||
|
||||
import android.Manifest;
|
||||
import android.app.AlarmManager;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.IAlarmManager;
|
||||
import android.app.PendingIntent;
|
||||
import android.app.admin.DeviceAdminInfo;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
@@ -97,9 +93,7 @@ import android.os.HandlerThread;
|
||||
import android.os.INetworkManagementService;
|
||||
import android.os.Message;
|
||||
import android.os.PowerManager;
|
||||
import android.os.Process;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.SystemClock;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
@@ -122,7 +116,6 @@ import com.android.internal.util.ArrayUtils;
|
||||
import com.android.internal.util.FileRotator;
|
||||
import com.android.internal.util.IndentingPrintWriter;
|
||||
import com.android.server.EventLogTags;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.connectivity.Tethering;
|
||||
|
||||
import java.io.File;
|
||||
@@ -484,18 +477,22 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
|
||||
@Override
|
||||
public int[] getRelevantUids() {
|
||||
enforcePermissionForManagedAdmin(mCallingPackage);
|
||||
return getUidComplete().getRelevantUids();
|
||||
return getUidComplete().getRelevantUids(checkAccessLevel(mCallingPackage));
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkStats getDeviceSummaryForNetwork(NetworkTemplate template, long start,
|
||||
long end) {
|
||||
enforcePermission(mCallingPackage);
|
||||
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
|
||||
if (accessLevel < NetworkStatsAccess.Level.DEVICE) {
|
||||
throw new SecurityException("Calling package " + mCallingPackage
|
||||
+ " cannot access device-level network stats");
|
||||
}
|
||||
NetworkStats result = new NetworkStats(end - start, 1);
|
||||
final long ident = Binder.clearCallingIdentity();
|
||||
try {
|
||||
result.combineAllValues(internalGetSummaryForNetwork(template, start, end));
|
||||
result.combineAllValues(
|
||||
internalGetSummaryForNetwork(template, start, end, accessLevel));
|
||||
} finally {
|
||||
Binder.restoreCallingIdentity(ident);
|
||||
}
|
||||
@@ -505,23 +502,25 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
@Override
|
||||
public NetworkStats getSummaryForNetwork(
|
||||
NetworkTemplate template, long start, long end) {
|
||||
enforcePermission(mCallingPackage);
|
||||
return internalGetSummaryForNetwork(template, start, end);
|
||||
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
|
||||
return internalGetSummaryForNetwork(template, start, end, accessLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkStatsHistory getHistoryForNetwork(NetworkTemplate template, int fields) {
|
||||
return internalGetHistoryForNetwork(template, fields);
|
||||
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
|
||||
return internalGetHistoryForNetwork(template, fields, accessLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public NetworkStats getSummaryForAllUid(
|
||||
NetworkTemplate template, long start, long end, boolean includeTags) {
|
||||
enforcePermissionForManagedAdmin(mCallingPackage);
|
||||
final NetworkStats stats = getUidComplete().getSummary(template, start, end);
|
||||
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
|
||||
final NetworkStats stats =
|
||||
getUidComplete().getSummary(template, start, end, accessLevel);
|
||||
if (includeTags) {
|
||||
final NetworkStats tagStats = getUidTagComplete()
|
||||
.getSummary(template, start, end);
|
||||
.getSummary(template, start, end, accessLevel);
|
||||
stats.combineAllValues(tagStats);
|
||||
}
|
||||
return stats;
|
||||
@@ -530,11 +529,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
@Override
|
||||
public NetworkStatsHistory getHistoryForUid(
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields) {
|
||||
enforcePermissionForManagedAdmin(mCallingPackage);
|
||||
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
|
||||
if (tag == TAG_NONE) {
|
||||
return getUidComplete().getHistory(template, uid, set, tag, fields);
|
||||
return getUidComplete().getHistory(template, uid, set, tag, fields,
|
||||
accessLevel);
|
||||
} else {
|
||||
return getUidTagComplete().getHistory(template, uid, set, tag, fields);
|
||||
return getUidTagComplete().getHistory(template, uid, set, tag, fields,
|
||||
accessLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -542,12 +543,13 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
public NetworkStatsHistory getHistoryIntervalForUid(
|
||||
NetworkTemplate template, int uid, int set, int tag, int fields,
|
||||
long start, long end) {
|
||||
enforcePermissionForManagedAdmin(mCallingPackage);
|
||||
@NetworkStatsAccess.Level int accessLevel = checkAccessLevel(mCallingPackage);
|
||||
if (tag == TAG_NONE) {
|
||||
return getUidComplete().getHistory(template, uid, set, tag, fields, start, end);
|
||||
return getUidComplete().getHistory(template, uid, set, tag, fields, start, end,
|
||||
accessLevel);
|
||||
} else {
|
||||
return getUidTagComplete().getHistory(template, uid, set, tag, fields,
|
||||
start, end);
|
||||
start, end, accessLevel);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -559,80 +561,42 @@ public class NetworkStatsService extends INetworkStatsService.Stub {
|
||||
};
|
||||
}
|
||||
|
||||
private boolean hasAppOpsPermission(String callingPackage) {
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
boolean appOpsAllow = false;
|
||||
if (callingPackage != null) {
|
||||
AppOpsManager appOps = (AppOpsManager) mContext.getSystemService(
|
||||
Context.APP_OPS_SERVICE);
|
||||
|
||||
final int mode = appOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
|
||||
callingUid, callingPackage);
|
||||
if (mode == AppOpsManager.MODE_DEFAULT) {
|
||||
// The default behavior here is to check if PackageManager has given the app
|
||||
// permission.
|
||||
final int permissionCheck = mContext.checkCallingPermission(
|
||||
Manifest.permission.PACKAGE_USAGE_STATS);
|
||||
appOpsAllow = permissionCheck == PackageManager.PERMISSION_GRANTED;
|
||||
}
|
||||
appOpsAllow = (mode == AppOpsManager.MODE_ALLOWED);
|
||||
}
|
||||
return appOpsAllow;
|
||||
private @NetworkStatsAccess.Level int checkAccessLevel(String callingPackage) {
|
||||
return NetworkStatsAccess.checkAccessLevel(
|
||||
mContext, Binder.getCallingUid(), callingPackage);
|
||||
}
|
||||
|
||||
private void enforcePermissionForManagedAdmin(String callingPackage) {
|
||||
boolean hasPermission = hasAppOpsPermission(callingPackage);
|
||||
if (!hasPermission) {
|
||||
// Profile and device owners are exempt from permission checking.
|
||||
final int callingUid = Binder.getCallingUid();
|
||||
final DevicePolicyManagerInternal dpmi = LocalServices.getService(
|
||||
DevicePolicyManagerInternal.class);
|
||||
|
||||
// Device owners are also profile owners so it is enough to check for that.
|
||||
if (dpmi != null && dpmi.isActiveAdminWithPolicy(callingUid,
|
||||
DeviceAdminInfo.USES_POLICY_PROFILE_OWNER)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!hasPermission) {
|
||||
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
|
||||
}
|
||||
}
|
||||
|
||||
private void enforcePermission(String callingPackage) {
|
||||
boolean appOpsAllow = hasAppOpsPermission(callingPackage);
|
||||
if (!appOpsAllow) {
|
||||
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Return network summary, splicing between DEV and XT stats when
|
||||
* appropriate.
|
||||
*/
|
||||
private NetworkStats internalGetSummaryForNetwork(
|
||||
NetworkTemplate template, long start, long end) {
|
||||
NetworkTemplate template, long start, long end,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
// We've been using pure XT stats long enough that we no longer need to
|
||||
// splice DEV and XT together.
|
||||
return mXtStatsCached.getSummary(template, start, end);
|
||||
return mXtStatsCached.getSummary(template, start, end, accessLevel);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return network history, splicing between DEV and XT stats when
|
||||
* appropriate.
|
||||
*/
|
||||
private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields) {
|
||||
private NetworkStatsHistory internalGetHistoryForNetwork(NetworkTemplate template, int fields,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
// We've been using pure XT stats long enough that we no longer need to
|
||||
// splice DEV and XT together.
|
||||
return mXtStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields);
|
||||
return mXtStatsCached.getHistory(template, UID_ALL, SET_ALL, TAG_NONE, fields, accessLevel);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long getNetworkTotalBytes(NetworkTemplate template, long start, long end) {
|
||||
// Special case - since this is for internal use only, don't worry about a full access level
|
||||
// check and just require the signature/privileged permission.
|
||||
mContext.enforceCallingOrSelfPermission(READ_NETWORK_USAGE_HISTORY, TAG);
|
||||
assertBandwidthControlEnabled();
|
||||
return internalGetSummaryForNetwork(template, start, end).getTotalBytes();
|
||||
return internalGetSummaryForNetwork(template, start, end, NetworkStatsAccess.Level.DEVICE)
|
||||
.getTotalBytes();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -0,0 +1,178 @@
|
||||
/*
|
||||
* Copyright (C) 2015 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;
|
||||
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import android.Manifest;
|
||||
import android.Manifest.permission;
|
||||
import android.app.AppOpsManager;
|
||||
import android.app.admin.DeviceAdminInfo;
|
||||
import android.app.admin.DevicePolicyManagerInternal;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.telephony.TelephonyManager;
|
||||
|
||||
import com.android.server.LocalServices;
|
||||
|
||||
import junit.framework.TestCase;
|
||||
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.MockitoAnnotations;
|
||||
|
||||
public class NetworkStatsAccessTest extends TestCase {
|
||||
private static final String TEST_PKG = "com.example.test";
|
||||
private static final int TEST_UID = 12345;
|
||||
|
||||
@Mock private Context mContext;
|
||||
@Mock private DevicePolicyManagerInternal mDpmi;
|
||||
@Mock private TelephonyManager mTm;
|
||||
@Mock private AppOpsManager mAppOps;
|
||||
|
||||
// Hold the real service so we can restore it when tearing down the test.
|
||||
private DevicePolicyManagerInternal mSystemDpmi;
|
||||
|
||||
@Override
|
||||
public void setUp() throws Exception {
|
||||
super.setUp();
|
||||
MockitoAnnotations.initMocks(this);
|
||||
|
||||
mSystemDpmi = LocalServices.getService(DevicePolicyManagerInternal.class);
|
||||
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
|
||||
LocalServices.addService(DevicePolicyManagerInternal.class, mDpmi);
|
||||
|
||||
when(mContext.getSystemService(Context.TELEPHONY_SERVICE)).thenReturn(mTm);
|
||||
when(mContext.getSystemService(Context.APP_OPS_SERVICE)).thenReturn(mAppOps);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tearDown() throws Exception {
|
||||
LocalServices.removeServiceForTest(DevicePolicyManagerInternal.class);
|
||||
LocalServices.addService(DevicePolicyManagerInternal.class, mSystemDpmi);
|
||||
super.tearDown();
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_hasCarrierPrivileges() throws Exception {
|
||||
setHasCarrierPrivileges(true);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEVICE,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_isDeviceOwner() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(true);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEVICE,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_isProfileOwner() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.USER,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_hasAppOpsBitAllowed() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_ALLOWED, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.USER,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_hasAppOpsBitDefault_grantedPermission() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, true);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.USER,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_hasReadHistoryPermission() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(true);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(true);
|
||||
assertEquals(NetworkStatsAccess.Level.USER,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_deniedAppOpsBit() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_ERRORED, true);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEFAULT,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
public void testCheckAccessLevel_deniedAppOpsBit_deniedPermission() throws Exception {
|
||||
setHasCarrierPrivileges(false);
|
||||
setIsDeviceOwner(false);
|
||||
setIsProfileOwner(false);
|
||||
setHasAppOpsPermission(AppOpsManager.MODE_DEFAULT, false);
|
||||
setHasReadHistoryPermission(false);
|
||||
assertEquals(NetworkStatsAccess.Level.DEFAULT,
|
||||
NetworkStatsAccess.checkAccessLevel(mContext, TEST_UID, TEST_PKG));
|
||||
}
|
||||
|
||||
private void setHasCarrierPrivileges(boolean hasPrivileges) {
|
||||
when(mTm.checkCarrierPrivilegesForPackage(TEST_PKG)).thenReturn(
|
||||
hasPrivileges ? TelephonyManager.CARRIER_PRIVILEGE_STATUS_HAS_ACCESS
|
||||
: TelephonyManager.CARRIER_PRIVILEGE_STATUS_NO_ACCESS);
|
||||
}
|
||||
|
||||
private void setIsDeviceOwner(boolean isOwner) {
|
||||
when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_DEVICE_OWNER))
|
||||
.thenReturn(isOwner);
|
||||
}
|
||||
|
||||
private void setIsProfileOwner(boolean isOwner) {
|
||||
when(mDpmi.isActiveAdminWithPolicy(TEST_UID, DeviceAdminInfo.USES_POLICY_PROFILE_OWNER))
|
||||
.thenReturn(isOwner);
|
||||
}
|
||||
|
||||
private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) {
|
||||
when(mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG))
|
||||
.thenReturn(appOpsMode);
|
||||
when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn(
|
||||
hasPermission ? PackageManager.PERMISSION_GRANTED
|
||||
: PackageManager.PERMISSION_DENIED);
|
||||
}
|
||||
|
||||
private void setHasReadHistoryPermission(boolean hasPermission) {
|
||||
when(mContext.checkCallingOrSelfPermission(permission.READ_NETWORK_USAGE_HISTORY))
|
||||
.thenReturn(hasPermission ? PackageManager.PERMISSION_GRANTED
|
||||
: PackageManager.PERMISSION_DENIED);
|
||||
}
|
||||
}
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
package com.android.server.net;
|
||||
|
||||
import static android.net.ConnectivityManager.TYPE_MOBILE;
|
||||
import static android.net.NetworkStats.SET_DEFAULT;
|
||||
import static android.net.NetworkStats.TAG_NONE;
|
||||
import static android.net.NetworkStats.UID_ALL;
|
||||
@@ -24,9 +25,14 @@ import static android.text.format.DateUtils.HOUR_IN_MILLIS;
|
||||
import static android.text.format.DateUtils.MINUTE_IN_MILLIS;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.net.NetworkIdentity;
|
||||
import android.net.NetworkStats;
|
||||
import android.net.NetworkTemplate;
|
||||
import android.os.Process;
|
||||
import android.os.UserHandle;
|
||||
import android.telephony.TelephonyManager;
|
||||
import android.test.AndroidTestCase;
|
||||
import android.test.MoreAsserts;
|
||||
import android.test.suitebuilder.annotation.MediumTest;
|
||||
|
||||
import com.android.frameworks.servicestests.R;
|
||||
@@ -68,7 +74,7 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
|
||||
|
||||
// verify that history read correctly
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
636016770L, 709306L, 88038768L, 518836L);
|
||||
636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// now export into a unified format
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
@@ -77,12 +83,12 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
|
||||
// clear structure completely
|
||||
collection.reset();
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
0L, 0L, 0L, 0L);
|
||||
0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// and read back into structure, verifying that totals are same
|
||||
collection.read(new ByteArrayInputStream(bos.toByteArray()));
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
636016770L, 709306L, 88038768L, 518836L);
|
||||
636016770L, 709306L, 88038768L, 518836L, NetworkStatsAccess.Level.DEVICE);
|
||||
}
|
||||
|
||||
public void testReadLegacyUid() throws Exception {
|
||||
@@ -94,7 +100,7 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
|
||||
|
||||
// verify that history read correctly
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
637076152L, 711413L, 88343717L, 521022L);
|
||||
637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// now export into a unified format
|
||||
final ByteArrayOutputStream bos = new ByteArrayOutputStream();
|
||||
@@ -103,12 +109,12 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
|
||||
// clear structure completely
|
||||
collection.reset();
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
0L, 0L, 0L, 0L);
|
||||
0L, 0L, 0L, 0L, NetworkStatsAccess.Level.DEVICE);
|
||||
|
||||
// and read back into structure, verifying that totals are same
|
||||
collection.read(new ByteArrayInputStream(bos.toByteArray()));
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI),
|
||||
637076152L, 711413L, 88343717L, 521022L);
|
||||
637076152L, 711413L, 88343717L, 521022L, NetworkStatsAccess.Level.DEVICE);
|
||||
}
|
||||
|
||||
public void testReadLegacyUidTags() throws Exception {
|
||||
@@ -151,6 +157,66 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
|
||||
assertEquals(2 * HOUR_IN_MILLIS, collection.getEndMillis());
|
||||
}
|
||||
|
||||
public void testAccessLevels() throws Exception {
|
||||
final NetworkStatsCollection collection = new NetworkStatsCollection(HOUR_IN_MILLIS);
|
||||
final NetworkStats.Entry entry = new NetworkStats.Entry();
|
||||
final NetworkIdentitySet identSet = new NetworkIdentitySet();
|
||||
identSet.add(new NetworkIdentity(TYPE_MOBILE, TelephonyManager.NETWORK_TYPE_UNKNOWN,
|
||||
TEST_IMSI, null, false));
|
||||
|
||||
int myUid = Process.myUid();
|
||||
int otherUidInSameUser = Process.myUid() + 1;
|
||||
int uidInDifferentUser = Process.myUid() + UserHandle.PER_USER_RANGE;
|
||||
|
||||
// Record one entry for the current UID.
|
||||
entry.rxBytes = 32;
|
||||
collection.recordData(identSet, myUid, SET_DEFAULT, TAG_NONE, 0, 60 * MINUTE_IN_MILLIS,
|
||||
entry);
|
||||
|
||||
// Record one entry for another UID in this user.
|
||||
entry.rxBytes = 64;
|
||||
collection.recordData(identSet, otherUidInSameUser, SET_DEFAULT, TAG_NONE, 0,
|
||||
60 * MINUTE_IN_MILLIS, entry);
|
||||
|
||||
// Record one entry for the system UID.
|
||||
entry.rxBytes = 128;
|
||||
collection.recordData(identSet, Process.SYSTEM_UID, SET_DEFAULT, TAG_NONE, 0,
|
||||
60 * MINUTE_IN_MILLIS, entry);
|
||||
|
||||
// Record one entry for a UID in a different user.
|
||||
entry.rxBytes = 256;
|
||||
collection.recordData(identSet, uidInDifferentUser, SET_DEFAULT, TAG_NONE, 0,
|
||||
60 * MINUTE_IN_MILLIS, entry);
|
||||
|
||||
// Verify the set of relevant UIDs for each access level.
|
||||
MoreAsserts.assertEquals(new int[] { myUid },
|
||||
collection.getRelevantUids(NetworkStatsAccess.Level.DEFAULT));
|
||||
MoreAsserts.assertEquals(new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser },
|
||||
collection.getRelevantUids(NetworkStatsAccess.Level.USER));
|
||||
MoreAsserts.assertEquals(
|
||||
new int[] { Process.SYSTEM_UID, myUid, otherUidInSameUser, uidInDifferentUser },
|
||||
collection.getRelevantUids(NetworkStatsAccess.Level.DEVICE));
|
||||
|
||||
// Verify security check in getHistory.
|
||||
assertNotNull(collection.getHistory(buildTemplateMobileAll(TEST_IMSI), myUid, SET_DEFAULT,
|
||||
TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT));
|
||||
try {
|
||||
collection.getHistory(buildTemplateMobileAll(TEST_IMSI), otherUidInSameUser,
|
||||
SET_DEFAULT, TAG_NONE, 0, NetworkStatsAccess.Level.DEFAULT);
|
||||
fail("Should have thrown SecurityException for accessing different UID");
|
||||
} catch (SecurityException e) {
|
||||
// expected
|
||||
}
|
||||
|
||||
// Verify appropriate aggregation in getSummary.
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32, 0, 0, 0,
|
||||
NetworkStatsAccess.Level.DEFAULT);
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128, 0, 0, 0,
|
||||
NetworkStatsAccess.Level.USER);
|
||||
assertSummaryTotal(collection, buildTemplateMobileAll(TEST_IMSI), 32 + 64 + 128 + 256, 0, 0,
|
||||
0, NetworkStatsAccess.Level.DEVICE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Copy a {@link Resources#openRawResource(int)} into {@link File} for
|
||||
* testing purposes.
|
||||
@@ -170,16 +236,19 @@ public class NetworkStatsCollectionTest extends AndroidTestCase {
|
||||
}
|
||||
|
||||
private static void assertSummaryTotal(NetworkStatsCollection collection,
|
||||
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
|
||||
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets,
|
||||
@NetworkStatsAccess.Level int accessLevel) {
|
||||
final NetworkStats.Entry entry = collection.getSummary(
|
||||
template, Long.MIN_VALUE, Long.MAX_VALUE).getTotal(null);
|
||||
template, Long.MIN_VALUE, Long.MAX_VALUE, accessLevel)
|
||||
.getTotal(null);
|
||||
assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
|
||||
}
|
||||
|
||||
private static void assertSummaryTotalIncludingTags(NetworkStatsCollection collection,
|
||||
NetworkTemplate template, long rxBytes, long rxPackets, long txBytes, long txPackets) {
|
||||
final NetworkStats.Entry entry = collection.getSummary(
|
||||
template, Long.MIN_VALUE, Long.MAX_VALUE).getTotalIncludingTags(null);
|
||||
template, Long.MIN_VALUE, Long.MAX_VALUE, NetworkStatsAccess.Level.DEVICE)
|
||||
.getTotalIncludingTags(null);
|
||||
assertEntry(entry, rxBytes, rxPackets, txBytes, txPackets);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user