diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 4ae3a0650b..26fef94788 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -264,6 +264,7 @@ public final class NetworkCapabilities implements Parcelable { mTransportInfo = null; mSignalStrength = SIGNAL_STRENGTH_UNSPECIFIED; mUids = null; + mAccessUids.clear(); mAdministratorUids = new int[0]; mOwnerUid = Process.INVALID_UID; mSSID = null; @@ -294,6 +295,7 @@ public final class NetworkCapabilities implements Parcelable { } mSignalStrength = nc.mSignalStrength; mUids = (nc.mUids == null) ? null : new ArraySet<>(nc.mUids); + setAccessUids(nc.mAccessUids); setAdministratorUids(nc.getAdministratorUids()); mOwnerUid = nc.mOwnerUid; mForbiddenNetworkCapabilities = nc.mForbiddenNetworkCapabilities; @@ -1025,6 +1027,7 @@ public final class NetworkCapabilities implements Parcelable { final int[] originalAdministratorUids = getAdministratorUids(); final TransportInfo originalTransportInfo = getTransportInfo(); final Set originalSubIds = getSubscriptionIds(); + final Set originalAccessUids = new ArraySet<>(mAccessUids); clearAll(); if (0 != (originalCapabilities & (1 << NET_CAPABILITY_NOT_RESTRICTED))) { // If the test network is not restricted, then it is only allowed to declare some @@ -1044,6 +1047,7 @@ public final class NetworkCapabilities implements Parcelable { mNetworkSpecifier = originalSpecifier; mSignalStrength = originalSignalStrength; mTransportInfo = originalTransportInfo; + mAccessUids.addAll(originalAccessUids); // Only retain the owner and administrator UIDs if they match the app registering the remote // caller that registered the network. @@ -1808,6 +1812,79 @@ public final class NetworkCapabilities implements Parcelable { return false; } + /** + * List of UIDs that can always access this network. + *

+ * UIDs in this list have access to this network, even if the network doesn't have the + * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission. + * This is only useful for restricted networks. For non-restricted networks it has no effect. + *

+ * This is disallowed in {@link NetworkRequest}, and can only be set by network agents. Network + * agents also have restrictions on how they can set theseĀ ; they can only back a public + * Android API. As such, Ethernet agents can set this when backing the per-UID access API, and + * Telephony can set exactly one UID which has to match the manager app for the associated + * subscription. Failure to comply with these rules will see this member cleared. + *

+ * This member is never null, but can be empty. + * @hide + */ + @NonNull + private final ArraySet mAccessUids = new ArraySet<>(); + + /** + * Set the list of UIDs that can always access this network. + * @param uids + * @hide + */ + public void setAccessUids(@NonNull final Set uids) { + // could happen with nc.set(nc), cheaper than always making a defensive copy + if (uids == mAccessUids) return; + + Objects.requireNonNull(uids); + mAccessUids.clear(); + mAccessUids.addAll(uids); + } + + /** + * The list of UIDs that can always access this network. + * + * The UIDs in this list can always access this network, even if it is restricted and + * the UID doesn't hold the USE_RESTRICTED_NETWORKS permission. This is defined by the + * network agent in charge of creating the network. + * + * Only network factories and the system server can see these UIDs, since the system + * server makes sure to redact them before sending a NetworkCapabilities to a process + * that doesn't hold the permission. + * + * @hide + */ + // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public @NonNull Set getAccessUids() { + return new ArraySet<>(mAccessUids); + } + + /** + * Test whether this UID has special permission to access this network, as per mAccessUids. + * @hide + */ + public boolean isAccessUid(int uid) { + return mAccessUids.contains(uid); + } + + /** + * @return whether any UID is in the list of access UIDs + * @hide + */ + public boolean hasAccessUids() { + return !mAccessUids.isEmpty(); + } + + private boolean equalsAccessUids(@NonNull NetworkCapabilities other) { + return mAccessUids.equals(other.mAccessUids); + } + /** * The SSID of the network, or null if not applicable or unknown. *

@@ -1962,6 +2039,7 @@ public final class NetworkCapabilities implements Parcelable { && equalsSpecifier(that) && equalsTransportInfo(that) && equalsUids(that) + && equalsAccessUids(that) && equalsSSID(that) && equalsOwnerUid(that) && equalsPrivateDnsBroken(that) @@ -1986,15 +2064,16 @@ public final class NetworkCapabilities implements Parcelable { + mSignalStrength * 29 + mOwnerUid * 31 + Objects.hashCode(mUids) * 37 - + Objects.hashCode(mSSID) * 41 - + Objects.hashCode(mTransportInfo) * 43 - + Objects.hashCode(mPrivateDnsBroken) * 47 - + Objects.hashCode(mRequestorUid) * 53 - + Objects.hashCode(mRequestorPackageName) * 59 - + Arrays.hashCode(mAdministratorUids) * 61 - + Objects.hashCode(mSubIds) * 67 - + Objects.hashCode(mUnderlyingNetworks) * 71 - + mEnterpriseId * 73; + + Objects.hashCode(mAccessUids) * 41 + + Objects.hashCode(mSSID) * 43 + + Objects.hashCode(mTransportInfo) * 47 + + Objects.hashCode(mPrivateDnsBroken) * 53 + + Objects.hashCode(mRequestorUid) * 59 + + Objects.hashCode(mRequestorPackageName) * 61 + + Arrays.hashCode(mAdministratorUids) * 67 + + Objects.hashCode(mSubIds) * 71 + + Objects.hashCode(mUnderlyingNetworks) * 73 + + mEnterpriseId * 79; } @Override @@ -2022,6 +2101,7 @@ public final class NetworkCapabilities implements Parcelable { dest.writeParcelable((Parcelable) mTransportInfo, flags); dest.writeInt(mSignalStrength); writeParcelableArraySet(dest, mUids, flags); + dest.writeIntArray(CollectionUtils.toIntArray(mAccessUids)); dest.writeString(mSSID); dest.writeBoolean(mPrivateDnsBroken); dest.writeIntArray(getAdministratorUids()); @@ -2034,7 +2114,7 @@ public final class NetworkCapabilities implements Parcelable { } public static final @android.annotation.NonNull Creator CREATOR = - new Creator() { + new Creator<>() { @Override public NetworkCapabilities createFromParcel(Parcel in) { NetworkCapabilities netCap = new NetworkCapabilities(); @@ -2048,6 +2128,11 @@ public final class NetworkCapabilities implements Parcelable { netCap.mTransportInfo = in.readParcelable(null); netCap.mSignalStrength = in.readInt(); netCap.mUids = readParcelableArraySet(in, null /* ClassLoader, null for default */); + final int[] accessUids = in.createIntArray(); + netCap.mAccessUids.ensureCapacity(accessUids.length); + for (int uid : accessUids) { + netCap.mAccessUids.add(uid); + } netCap.mSSID = in.readString(); netCap.mPrivateDnsBroken = in.readBoolean(); netCap.setAdministratorUids(in.createIntArray()); @@ -2124,6 +2209,11 @@ public final class NetworkCapabilities implements Parcelable { sb.append(" Uids: <").append(mUids).append(">"); } } + + if (hasAccessUids()) { + sb.append(" AccessUids: <").append(mAccessUids).append(">"); + } + if (mOwnerUid != Process.INVALID_UID) { sb.append(" OwnerUid: ").append(mOwnerUid); } @@ -2300,7 +2390,7 @@ public final class NetworkCapabilities implements Parcelable { private static void checkValidCapability(@NetworkCapabilities.NetCapability int capability) { if (!isValidCapability(capability)) { - throw new IllegalArgumentException("NetworkCapability " + capability + "out of range"); + throw new IllegalArgumentException("NetworkCapability " + capability + " out of range"); } } @@ -2908,6 +2998,44 @@ public final class NetworkCapabilities implements Parcelable { return this; } + /** + * Set a list of UIDs that can always access this network + *

+ * Provide a list of UIDs that can access this network even if the network doesn't have the + * {@link #NET_CAPABILITY_NOT_RESTRICTED} capability and the UID does not hold the + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission. + *

+ * This is disallowed in {@link NetworkRequest}, and can only be set by + * {@link NetworkAgent}s, who hold the + * {@link android.Manifest.permission.NETWORK_FACTORY} permission. + * Network agents also have restrictions on how they can set theseĀ ; they can only back + * a public Android API. As such, Ethernet agents can set this when backing the per-UID + * access API, and Telephony can set exactly one UID which has to match the manager app for + * the associated subscription. Failure to comply with these rules will see this member + * cleared. + *

+ * Only network factories and the system server can see these UIDs, since the system server + * makes sure to redact them before sending a {@link NetworkCapabilities} instance to a + * process that doesn't hold the {@link android.Manifest.permission.NETWORK_FACTORY} + * permission. + *

+ * This list cannot be null, but it can be empty to mean that no UID without the + * {@link android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS} permission + * gets to access this network. + * + * @param uids the list of UIDs that can always access this network + * @return this builder + * @hide + */ + @NonNull + // @SystemApi(client = SystemApi.Client.MODULE_LIBRARIES) + @RequiresPermission(android.Manifest.permission.NETWORK_FACTORY) + public Builder setAccessUids(@NonNull Set uids) { + Objects.requireNonNull(uids); + mCaps.setAccessUids(uids); + return this; + } + /** * Set the underlying networks of this network. * diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 35933be5dc..f1c1499c21 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -2099,6 +2099,7 @@ public class ConnectivityService extends IConnectivityManager.Stub newNc.setAdministratorUids(new int[0]); if (!checkAnyPermissionOf( callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) { + newNc.setAccessUids(new ArraySet<>()); newNc.setSubscriptionIds(Collections.emptySet()); } @@ -6210,6 +6211,9 @@ public class ConnectivityService extends IConnectivityManager.Stub if (nc.isPrivateDnsBroken()) { throw new IllegalArgumentException("Can't request broken private DNS"); } + if (nc.hasAccessUids()) { + throw new IllegalArgumentException("Can't request access UIDs"); + } } // TODO: Set the mini sdk to 31 and remove @TargetApi annotation when b/205923322 is addressed. diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java index a6972f960b..76eaf224db 100644 --- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java +++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java @@ -53,6 +53,7 @@ import android.os.RemoteException; import android.os.SystemClock; import android.telephony.data.EpsBearerQosSessionAttributes; import android.telephony.data.NrQosSessionAttributes; +import android.util.ArraySet; import android.util.Log; import android.util.Pair; import android.util.SparseArray; @@ -1200,6 +1201,19 @@ public class NetworkAgentInfo implements Comparable, NetworkRa if (nc.hasTransport(TRANSPORT_TEST)) { nc.restrictCapabilitiesForTestNetwork(creatorUid); } + if (!areAccessUidsAcceptableFromNetworkAgent(nc)) { + nc.setAccessUids(new ArraySet<>()); + } + } + + private static boolean areAccessUidsAcceptableFromNetworkAgent( + @NonNull final NetworkCapabilities nc) { + if (nc.hasAccessUids()) { + Log.w(TAG, "Capabilities from network agent must not contain access UIDs"); + // TODO : accept the supported cases + return false; + } + return true; } // TODO: Print shorter members first and only print the boolean variable which value is true diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java index bea00a9171..742044b8d2 100644 --- a/tests/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java @@ -308,6 +308,48 @@ public class NetworkCapabilitiesTest { } } + @Test @IgnoreUpTo(SC_V2) + public void testSetAccessUids() { + final NetworkCapabilities nc = new NetworkCapabilities(); + assertThrows(NullPointerException.class, () -> nc.setAccessUids(null)); + assertFalse(nc.hasAccessUids()); + assertFalse(nc.isAccessUid(0)); + assertFalse(nc.isAccessUid(1000)); + assertEquals(0, nc.getAccessUids().size()); + nc.setAccessUids(new ArraySet<>()); + assertFalse(nc.hasAccessUids()); + assertFalse(nc.isAccessUid(0)); + assertFalse(nc.isAccessUid(1000)); + assertEquals(0, nc.getAccessUids().size()); + + final ArraySet uids = new ArraySet<>(); + uids.add(200); + uids.add(250); + uids.add(-1); + uids.add(Integer.MAX_VALUE); + nc.setAccessUids(uids); + assertNotEquals(nc, new NetworkCapabilities()); + assertTrue(nc.hasAccessUids()); + + final List includedList = List.of(-2, 0, 199, 700, 901, 1000, Integer.MIN_VALUE); + final List excludedList = List.of(-1, 200, 250, Integer.MAX_VALUE); + for (final int uid : includedList) { + assertFalse(nc.isAccessUid(uid)); + } + for (final int uid : excludedList) { + assertTrue(nc.isAccessUid(uid)); + } + + final Set outUids = nc.getAccessUids(); + assertEquals(4, outUids.size()); + for (final int uid : includedList) { + assertFalse(outUids.contains(uid)); + } + for (final int uid : excludedList) { + assertTrue(outUids.contains(uid)); + } + } + @Test public void testParcelNetworkCapabilities() { final Set> uids = new ArraySet<>(); @@ -318,6 +360,10 @@ public class NetworkCapabilitiesTest { .addCapability(NET_CAPABILITY_EIMS) .addCapability(NET_CAPABILITY_NOT_METERED); if (isAtLeastS()) { + final ArraySet accessUids = new ArraySet<>(); + accessUids.add(4); + accessUids.add(9); + netCap.setAccessUids(accessUids); netCap.setSubscriptionIds(Set.of(TEST_SUBID1, TEST_SUBID2)); netCap.setUids(uids); } diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index ef5dc77386..344482bfbf 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -23,7 +23,6 @@ import android.net.INetworkAgent import android.net.INetworkAgentRegistry import android.net.InetAddresses import android.net.IpPrefix -import android.net.KeepalivePacketData import android.net.LinkAddress import android.net.LinkProperties import android.net.NattKeepalivePacketData @@ -42,6 +41,7 @@ import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VCN_MANAGED import android.net.NetworkCapabilities.NET_CAPABILITY_NOT_VPN import android.net.NetworkCapabilities.NET_CAPABILITY_TEMPORARILY_NOT_METERED import android.net.NetworkCapabilities.NET_CAPABILITY_TRUSTED +import android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED import android.net.NetworkCapabilities.TRANSPORT_TEST import android.net.NetworkCapabilities.TRANSPORT_VPN import android.net.NetworkInfo @@ -53,7 +53,6 @@ import android.net.RouteInfo import android.net.QosCallback import android.net.QosCallbackException import android.net.QosCallback.QosCallbackRegistrationException -import android.net.QosFilter import android.net.QosSession import android.net.QosSessionAttributes import android.net.QosSocketInfo @@ -67,7 +66,6 @@ import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosS import android.os.Build import android.os.Handler import android.os.HandlerThread -import android.os.Looper import android.os.Message import android.os.SystemClock import android.telephony.TelephonyManager @@ -82,6 +80,8 @@ import com.android.testutils.CompatUtil import com.android.testutils.DevSdkIgnoreRule.IgnoreUpTo import com.android.testutils.DevSdkIgnoreRunner import com.android.testutils.RecorderCallback.CallbackEntry.Available +import com.android.testutils.RecorderCallback.CallbackEntry.BlockedStatus +import com.android.testutils.RecorderCallback.CallbackEntry.LinkPropertiesChanged import com.android.testutils.RecorderCallback.CallbackEntry.Losing import com.android.testutils.RecorderCallback.CallbackEntry.Lost import com.android.testutils.TestableNetworkAgent @@ -100,7 +100,6 @@ import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosC import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus import com.android.testutils.TestableNetworkCallback import org.junit.After -import org.junit.Assert.assertArrayEquals import org.junit.Assume.assumeFalse import org.junit.Before import org.junit.Test @@ -462,6 +461,36 @@ class NetworkAgentTest { } } + private fun ncWithAccessUids(vararg uids: Int) = NetworkCapabilities.Builder() + .addTransportType(TRANSPORT_TEST) + .setAccessUids(uids.toSet()).build() + + @Test + fun testRejectedUpdates() { + val callback = TestableNetworkCallback() + // will be cleaned up in tearDown + registerNetworkCallback(makeTestNetworkRequest(), callback) + val agent = createNetworkAgent(initialNc = ncWithAccessUids(200)) + agent.register() + agent.markConnected() + + // Make sure the UIDs have been ignored. + callback.expectCallback(agent.network!!) + callback.expectCapabilitiesThat(agent.network!!) { + it.accessUids.isEmpty() && !it.hasCapability(NET_CAPABILITY_VALIDATED) + } + callback.expectCallback(agent.network!!) + callback.expectCallback(agent.network!!) + callback.expectCapabilitiesThat(agent.network!!) { + it.accessUids.isEmpty() && it.hasCapability(NET_CAPABILITY_VALIDATED) + } + callback.assertNoCallback(NO_CALLBACK_TIMEOUT) + + // Make sure that the UIDs are also ignored upon update + agent.sendNetworkCapabilities(ncWithAccessUids(200, 300)) + callback.assertNoCallback(NO_CALLBACK_TIMEOUT) + } + @Test fun testSendScore() { // This test will create two networks and check that the one with the stronger diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index 8f67e485a7..f7ffd7967e 100644 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -3813,20 +3813,50 @@ public class ConnectivityServiceTest { public void testNoMutableNetworkRequests() throws Exception { final PendingIntent pendingIntent = PendingIntent.getBroadcast( mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); - NetworkRequest request1 = new NetworkRequest.Builder() + final NetworkRequest request1 = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_VALIDATED) .build(); - NetworkRequest request2 = new NetworkRequest.Builder() + final NetworkRequest request2 = new NetworkRequest.Builder() .addCapability(NET_CAPABILITY_CAPTIVE_PORTAL) .build(); - Class expected = IllegalArgumentException.class; + final Class expected = IllegalArgumentException.class; assertThrows(expected, () -> mCm.requestNetwork(request1, new NetworkCallback())); assertThrows(expected, () -> mCm.requestNetwork(request1, pendingIntent)); assertThrows(expected, () -> mCm.requestNetwork(request2, new NetworkCallback())); assertThrows(expected, () -> mCm.requestNetwork(request2, pendingIntent)); } + @Test + public void testNoAccessUidsInNetworkRequests() throws Exception { + final PendingIntent pendingIntent = PendingIntent.getBroadcast( + mContext, 0 /* requestCode */, new Intent("a"), FLAG_IMMUTABLE); + final NetworkRequest r = new NetworkRequest.Builder().build(); + final ArraySet accessUids = new ArraySet<>(); + accessUids.add(6); + accessUids.add(9); + r.networkCapabilities.setAccessUids(accessUids); + + final Handler handler = new Handler(ConnectivityThread.getInstanceLooper()); + final NetworkCallback cb = new NetworkCallback(); + + final Class expected = IllegalArgumentException.class; + assertThrows(expected, () -> mCm.requestNetwork(r, cb)); + assertThrows(expected, () -> mCm.requestNetwork(r, pendingIntent)); + assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb)); + assertThrows(expected, () -> mCm.registerNetworkCallback(r, cb, handler)); + assertThrows(expected, () -> mCm.registerNetworkCallback(r, pendingIntent)); + assertThrows(expected, () -> mCm.registerBestMatchingNetworkCallback(r, cb, handler)); + + // Make sure that resetting the access UIDs to the empty set will allow calling + // requestNetwork and registerNetworkCallback. + r.networkCapabilities.setAccessUids(Collections.emptySet()); + mCm.requestNetwork(r, cb); + mCm.unregisterNetworkCallback(cb); + mCm.registerNetworkCallback(r, cb); + mCm.unregisterNetworkCallback(cb); + } + @Test public void testMMSonWiFi() throws Exception { // Test bringing up cellular without MMS NetworkRequest gets reaped