diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index a8e81791fe..65dfb13237 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -71,6 +71,7 @@ public final class NetworkCapabilities implements Parcelable { mUids = nc.mUids; mEstablishingVpnAppUid = nc.mEstablishingVpnAppUid; mUnwantedNetworkCapabilities = nc.mUnwantedNetworkCapabilities; + mSSID = nc.mSSID; } } @@ -86,6 +87,7 @@ public final class NetworkCapabilities implements Parcelable { mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; mUids = null; mEstablishingVpnAppUid = INVALID_UID; + mSSID = null; } /** @@ -921,7 +923,7 @@ public final class NetworkCapabilities implements Parcelable { /** * Sets the signal strength. This is a signed integer, with higher values indicating a stronger * signal. The exact units are bearer-dependent. For example, Wi-Fi uses the same RSSI units - * reported by WifiManager. + * reported by wifi code. *

* Note that when used to register a network callback, this specifies the minimum acceptable * signal strength. When received as the state of an existing network it specifies the current @@ -1053,7 +1055,7 @@ public final class NetworkCapabilities implements Parcelable { } /** - * Tests if the set of UIDs that this network applies to is the same of the passed set of UIDs. + * Tests if the set of UIDs that this network applies to is the same as the passed network. *

* This test only checks whether equal range objects are in both sets. It will * return false if the ranges are not exactly the same, even if the covered UIDs @@ -1143,6 +1145,62 @@ public final class NetworkCapabilities implements Parcelable { mUids.addAll(nc.mUids); } + + /** + * The SSID of the network, or null if not applicable or unknown. + *

+ * This is filled in by wifi code. + * @hide + */ + private String mSSID; + + /** + * Sets the SSID of this network. + * @hide + */ + public NetworkCapabilities setSSID(String ssid) { + mSSID = ssid; + return this; + } + + /** + * Gets the SSID of this network, or null if none or unknown. + * @hide + */ + public String getSSID() { + return mSSID; + } + + /** + * Tests if the SSID of this network is the same as the SSID of the passed network. + * @hide + */ + public boolean equalsSSID(NetworkCapabilities nc) { + return Objects.equals(mSSID, nc.mSSID); + } + + /** + * Check if the SSID requirements of this object are matched by the passed object. + * @hide + */ + public boolean satisfiedBySSID(NetworkCapabilities nc) { + return mSSID == null || mSSID.equals(nc.mSSID); + } + + /** + * Combine SSIDs of the capabilities. + *

+ * This is only legal if either the SSID of this object is null, or both SSIDs are + * equal. + * @hide + */ + private void combineSSIDs(NetworkCapabilities nc) { + if (mSSID != null && !mSSID.equals(nc.mSSID)) { + throw new IllegalStateException("Can't combine two SSIDs"); + } + setSSID(nc.mSSID); + } + /** * Combine a set of Capabilities to this one. Useful for coming up with the complete set * @hide @@ -1154,6 +1212,7 @@ public final class NetworkCapabilities implements Parcelable { combineSpecifiers(nc); combineSignalStrength(nc); combineUids(nc); + combineSSIDs(nc); } /** @@ -1172,7 +1231,8 @@ public final class NetworkCapabilities implements Parcelable { && (onlyImmutable || satisfiedByLinkBandwidths(nc)) && satisfiedBySpecifier(nc) && (onlyImmutable || satisfiedBySignalStrength(nc)) - && (onlyImmutable || satisfiedByUids(nc))); + && (onlyImmutable || satisfiedByUids(nc)) + && (onlyImmutable || satisfiedBySSID(nc))); } /** @@ -1259,7 +1319,8 @@ public final class NetworkCapabilities implements Parcelable { && equalsLinkBandwidths(that) && equalsSignalStrength(that) && equalsSpecifier(that) - && equalsUids(that)); + && equalsUids(that) + && equalsSSID(that)); } @Override @@ -1274,7 +1335,8 @@ public final class NetworkCapabilities implements Parcelable { + (mLinkDownBandwidthKbps * 19) + Objects.hashCode(mNetworkSpecifier) * 23 + (mSignalStrength * 29) - + Objects.hashCode(mUids) * 31; + + Objects.hashCode(mUids) * 31 + + Objects.hashCode(mSSID) * 37; } @Override @@ -1291,6 +1353,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeParcelable((Parcelable) mNetworkSpecifier, flags); dest.writeInt(mSignalStrength); dest.writeArraySet(mUids); + dest.writeString(mSSID); } public static final Creator CREATOR = @@ -1308,6 +1371,7 @@ public final class NetworkCapabilities implements Parcelable { netCap.mSignalStrength = in.readInt(); netCap.mUids = (ArraySet) in.readArraySet( null /* ClassLoader, null for default */); + netCap.mSSID = in.readString(); return netCap; } @Override @@ -1358,6 +1422,10 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" EstablishingAppUid: ").append(mEstablishingVpnAppUid); } + if (null != mSSID) { + sb.append(" SSID: ").append(mSSID); + } + sb.append("]"); return sb.toString(); } diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 6463bed639..079d815599 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -1383,7 +1383,8 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nai != null) { synchronized (nai) { if (nai.networkCapabilities != null) { - return networkCapabilitiesWithoutUidsUnlessAllowed(nai.networkCapabilities, + return networkCapabilitiesRestrictedForCallerPermissions( + nai.networkCapabilities, Binder.getCallingPid(), Binder.getCallingUid()); } } @@ -1397,10 +1398,12 @@ public class ConnectivityService extends IConnectivityManager.Stub return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network)); } - private NetworkCapabilities networkCapabilitiesWithoutUidsUnlessAllowed( + private NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions( NetworkCapabilities nc, int callerPid, int callerUid) { - if (checkSettingsPermission(callerPid, callerUid)) return new NetworkCapabilities(nc); - return new NetworkCapabilities(nc).setUids(null); + final NetworkCapabilities newNc = new NetworkCapabilities(nc); + if (!checkSettingsPermission(callerPid, callerUid)) newNc.setUids(null); + if (!checkNetworkStackPermission(callerPid, callerUid)) newNc.setSSID(null); + return newNc; } private void restrictRequestUidsForCaller(NetworkCapabilities nc) { @@ -1659,6 +1662,11 @@ public class ConnectivityService extends IConnectivityManager.Stub android.Manifest.permission.NETWORK_SETTINGS, pid, uid); } + private boolean checkNetworkStackPermission(int pid, int uid) { + return PERMISSION_GRANTED == mContext.checkPermission( + android.Manifest.permission.NETWORK_STACK, pid, uid); + } + private void enforceTetherAccessPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.ACCESS_NETWORK_STATE, @@ -4235,6 +4243,15 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + // This checks that the passed capabilities either do not request a specific SSID, or the + // calling app has permission to do so. + private void ensureSufficientPermissionsForRequest(NetworkCapabilities nc, + int callerPid, int callerUid) { + if (null != nc.getSSID() && !checkNetworkStackPermission(callerPid, callerUid)) { + throw new SecurityException("Insufficient permissions to request a specific SSID"); + } + } + private ArrayList getSignalStrengthThresholds(NetworkAgentInfo nai) { final SortedSet thresholds = new TreeSet(); synchronized (nai) { @@ -4304,6 +4321,8 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceMeteredApnPolicy(networkCapabilities); } ensureRequestableCapabilities(networkCapabilities); + ensureSufficientPermissionsForRequest(networkCapabilities, + Binder.getCallingPid(), Binder.getCallingUid()); // Set the UID range for this request to the single UID of the requester, or to an empty // set of UIDs if the caller has the appropriate permission and UIDs have not been set. // This will overwrite any allowed UIDs in the requested capabilities. Though there @@ -4382,6 +4401,8 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceNetworkRequestPermissions(networkCapabilities); enforceMeteredApnPolicy(networkCapabilities); ensureRequestableCapabilities(networkCapabilities); + ensureSufficientPermissionsForRequest(networkCapabilities, + Binder.getCallingPid(), Binder.getCallingUid()); ensureValidNetworkSpecifier(networkCapabilities); restrictRequestUidsForCaller(networkCapabilities); @@ -4437,6 +4458,8 @@ public class ConnectivityService extends IConnectivityManager.Stub } NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); + ensureSufficientPermissionsForRequest(networkCapabilities, + Binder.getCallingPid(), Binder.getCallingUid()); restrictRequestUidsForCaller(nc); // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get @@ -4463,6 +4486,8 @@ public class ConnectivityService extends IConnectivityManager.Stub enforceAccessPermission(); } ensureValidNetworkSpecifier(networkCapabilities); + ensureSufficientPermissionsForRequest(networkCapabilities, + Binder.getCallingPid(), Binder.getCallingUid()); final NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities); restrictRequestUidsForCaller(nc); @@ -5034,7 +5059,7 @@ public class ConnectivityService extends IConnectivityManager.Stub } case ConnectivityManager.CALLBACK_CAP_CHANGED: { // networkAgent can't be null as it has been accessed a few lines above. - final NetworkCapabilities nc = networkCapabilitiesWithoutUidsUnlessAllowed( + final NetworkCapabilities nc = networkCapabilitiesRestrictedForCallerPermissions( networkAgent.networkCapabilities, nri.mPid, nri.mUid); putParcelable(bundle, nc); break; diff --git a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java index 0d935dba22..02459bde09 100644 --- a/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java +++ b/services/core/java/com/android/server/connectivity/NetworkNotificationManager.java @@ -23,6 +23,7 @@ import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.net.NetworkCapabilities; +import android.net.wifi.WifiInfo; import android.os.UserHandle; import android.telephony.TelephonyManager; import android.util.Slog; @@ -176,7 +177,8 @@ public class NetworkNotificationManager { switch (transportType) { case TRANSPORT_WIFI: title = r.getString(R.string.wifi_available_sign_in, 0); - details = r.getString(R.string.network_available_sign_in_detailed, extraInfo); + details = r.getString(R.string.network_available_sign_in_detailed, + WifiInfo.removeDoubleQuotes(nai.networkCapabilities.getSSID())); break; case TRANSPORT_CELLULAR: title = r.getString(R.string.network_available_sign_in, 0); diff --git a/tests/net/java/android/net/NetworkCapabilitiesTest.java b/tests/net/java/android/net/NetworkCapabilitiesTest.java index cdb4307d70..da897ae9ea 100644 --- a/tests/net/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/net/java/android/net/NetworkCapabilitiesTest.java @@ -39,12 +39,14 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; import android.os.Parcel; import android.support.test.runner.AndroidJUnit4; import android.test.suitebuilder.annotation.SmallTest; import android.util.ArraySet; + import org.junit.Test; import org.junit.runner.RunWith; @@ -53,6 +55,8 @@ import java.util.Set; @RunWith(AndroidJUnit4.class) @SmallTest public class NetworkCapabilitiesTest { + private static final String TEST_SSID = "TEST_SSID"; + @Test public void testMaybeMarkCapabilitiesRestricted() { // verify EIMS is restricted @@ -259,6 +263,8 @@ public class NetworkCapabilitiesTest { .addCapability(NET_CAPABILITY_EIMS) .addCapability(NET_CAPABILITY_NOT_METERED); assertEqualsThroughMarshalling(netCap); + netCap.setSSID(TEST_SSID); + assertEqualsThroughMarshalling(netCap); } @Test @@ -353,6 +359,21 @@ public class NetworkCapabilitiesTest { assertTrue(nc1.equalsNetCapabilities(nc2)); } + @Test + public void testSSID() { + NetworkCapabilities nc1 = new NetworkCapabilities(); + NetworkCapabilities nc2 = new NetworkCapabilities(); + assertTrue(nc2.satisfiedBySSID(nc1)); + + nc1.setSSID(TEST_SSID); + assertTrue(nc2.satisfiedBySSID(nc1)); + nc2.setSSID("different " + TEST_SSID); + assertFalse(nc2.satisfiedBySSID(nc1)); + + assertTrue(nc1.satisfiedByImmutableNetworkCapabilities(nc2)); + assertFalse(nc1.satisfiedByNetworkCapabilities(nc2)); + } + @Test public void testCombineCapabilities() { NetworkCapabilities nc1 = new NetworkCapabilities(); @@ -374,6 +395,19 @@ public class NetworkCapabilitiesTest { // will never be satisfied. assertTrue(nc2.hasCapability(NET_CAPABILITY_NOT_ROAMING)); assertTrue(nc2.hasUnwantedCapability(NET_CAPABILITY_NOT_ROAMING)); + + nc1.setSSID(TEST_SSID); + nc2.combineCapabilities(nc1); + assertTrue(TEST_SSID.equals(nc2.getSSID())); + + // Because they now have the same SSID, the folllowing call should not throw + nc2.combineCapabilities(nc1); + + nc1.setSSID("different " + TEST_SSID); + try { + nc2.combineCapabilities(nc1); + fail("Expected IllegalStateException: can't combine different SSIDs"); + } catch (IllegalStateException expected) {} } @Test diff --git a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java index 23318c29df..b870bbdb73 100644 --- a/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java +++ b/tests/net/java/com/android/server/net/NetworkStatsAccessTest.java @@ -176,7 +176,7 @@ public class NetworkStatsAccessTest { } private void setHasAppOpsPermission(int appOpsMode, boolean hasPermission) { - when(mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG)) + when(mAppOps.noteOp(AppOpsManager.OP_GET_USAGE_STATS, TEST_UID, TEST_PKG)) .thenReturn(appOpsMode); when(mContext.checkCallingPermission(Manifest.permission.PACKAGE_USAGE_STATS)).thenReturn( hasPermission ? PackageManager.PERMISSION_GRANTED