diff --git a/core/java/android/app/usage/NetworkStatsManager.java b/core/java/android/app/usage/NetworkStatsManager.java index 2886cda9d2..0fce7a90ea 100644 --- a/core/java/android/app/usage/NetworkStatsManager.java +++ b/core/java/android/app/usage/NetworkStatsManager.java @@ -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. *

* 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. *

* Summary queries *

@@ -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}. *

- * NOTE: This API requires the permission + * NOTE: 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. + *

+ * 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. + *

+ * NOTE: 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"; diff --git a/services/core/java/com/android/server/net/NetworkStatsAccess.java b/services/core/java/com/android/server/net/NetworkStatsAccess.java new file mode 100644 index 0000000000..53ba7184e0 --- /dev/null +++ b/services/core/java/com/android/server/net/NetworkStatsAccess.java @@ -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. + * + *

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. + * + *

Can only access usage for one's own UID. + * + *

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. + * + *

Granted to: + *

+ */ + int USER = 1; + + /** + * Access level for apps which can access usage for any app on the device, including apps + * running on other users/profiles. + * + *

Granted to: + *

+ */ + 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; + } +} diff --git a/services/core/java/com/android/server/net/NetworkStatsCollection.java b/services/core/java/com/android/server/net/NetworkStatsCollection.java index 15b68c7399..102695e343 100644 --- a/services/core/java/com/android/server/net/NetworkStatsCollection.java +++ b/services/core/java/com/android/server/net/NetworkStatsCollection.java @@ -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}. diff --git a/services/core/java/com/android/server/net/NetworkStatsRecorder.java b/services/core/java/com/android/server/net/NetworkStatsRecorder.java index 649086575c..c09196006b 100644 --- a/services/core/java/com/android/server/net/NetworkStatsRecorder.java +++ b/services/core/java/com/android/server/net/NetworkStatsRecorder.java @@ -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); } /** diff --git a/services/core/java/com/android/server/net/NetworkStatsService.java b/services/core/java/com/android/server/net/NetworkStatsService.java index 844934846f..b1d6f896dd 100644 --- a/services/core/java/com/android/server/net/NetworkStatsService.java +++ b/services/core/java/com/android/server/net/NetworkStatsService.java @@ -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