From 5301778ad8d3b86d7475f1a0f3e1c831cba8acb6 Mon Sep 17 00:00:00 2001 From: Chalard Jean Date: Thu, 24 Nov 2022 17:13:44 +0900 Subject: [PATCH] Add more powerful CTS for allowedUids MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit This tests that the network providers are subject to appropriate limitations as to what they can set as allowedUids – both in the positive (can do) case, and the negative (can't do) case. Test: this Change-Id: I115e2a4bc02ddcd03ecf2f35130fcb0378da22bd --- .../src/android/net/NetworkCapabilities.java | 12 + .../android/server/ConnectivityService.java | 15 +- .../CarrierPrivilegeAuthenticator.java | 21 +- .../server/connectivity/NetworkAgentInfo.java | 25 +- tests/cts/net/Android.bp | 19 +- tests/cts/net/AndroidManifest.xml | 3 +- tests/cts/net/AndroidTestTemplate.xml | 1 + .../carrierservicepackage/AndroidManifest.xml | 36 ++ .../DummyCarrierConfigService.java | 45 +++ .../net/cts/ConnectivityManagerTest.java | 5 +- .../src/android/net/cts/NetworkAgentTest.kt | 323 ++++++++++++++++-- .../server/ConnectivityServiceTest.java | 1 + 12 files changed, 460 insertions(+), 46 deletions(-) create mode 100644 tests/cts/net/carrierservicepackage/AndroidManifest.xml create mode 100644 tests/cts/net/carrierservicepackage/src/android/net/cts/carrierservicepackage/DummyCarrierConfigService.java diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index f959114451..efae754caa 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -1457,6 +1457,18 @@ public final class NetworkCapabilities implements Parcelable { return mTransportTypes == (1 << transportType); } + /** + * Returns true iff this NC has the specified transport and no other, ignoring TRANSPORT_TEST. + * + * If this NC has the passed transport and no other, this method returns true. + * If this NC has the passed transport, TRANSPORT_TEST and no other, this method returns true. + * Otherwise, this method returns false. + * @hide + */ + public boolean hasSingleTransportBesidesTest(@Transport int transportType) { + return (mTransportTypes & ~(1 << TRANSPORT_TEST)) == (1 << transportType); + } + private boolean satisfiedByTransportTypes(NetworkCapabilities nc) { return ((this.mTransportTypes == 0) || ((this.mTransportTypes & nc.mTransportTypes) != 0)); diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index ea6d37e080..6a41bc6b79 100755 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -2649,6 +2649,13 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + private boolean canSeeAllowedUids(final int pid, final int uid, final int netOwnerUid) { + return Process.SYSTEM_UID == uid + || netOwnerUid == uid + || checkAnyPermissionOf(mContext, pid, uid, + android.Manifest.permission.NETWORK_FACTORY); + } + @VisibleForTesting NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions( NetworkCapabilities nc, int callerPid, int callerUid) { @@ -2670,8 +2677,7 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK)) { newNc.setAdministratorUids(new int[0]); } - if (!checkAnyPermissionOf(mContext, - callerPid, callerUid, android.Manifest.permission.NETWORK_FACTORY)) { + if (!canSeeAllowedUids(callerPid, callerUid, newNc.getOwnerUid())) { newNc.setAllowedUids(new ArraySet<>()); newNc.setSubscriptionIds(Collections.emptySet()); } @@ -3944,6 +3950,11 @@ public class ConnectivityService extends IConnectivityManager.Stub pw.println(); dumpBpfProgramStatus(pw); + if (null != mCarrierPrivilegeAuthenticator) { + pw.println(); + mCarrierPrivilegeAuthenticator.dump(pw); + } + pw.println(); if (!CollectionUtils.contains(args, SHORT_ARG)) { diff --git a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java index ab7b1a7902..8edceb099f 100644 --- a/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java +++ b/service/src/com/android/server/connectivity/CarrierPrivilegeAuthenticator.java @@ -41,6 +41,7 @@ import android.util.SparseIntArray; import com.android.internal.annotations.GuardedBy; import com.android.internal.annotations.VisibleForTesting; +import com.android.internal.util.IndentingPrintWriter; import com.android.modules.utils.HandlerExecutor; import com.android.networkstack.apishim.TelephonyManagerShimImpl; import com.android.networkstack.apishim.common.TelephonyManagerShim; @@ -125,11 +126,13 @@ public class CarrierPrivilegeAuthenticator { private class PrivilegeListener implements CarrierPrivilegesListenerShim { public final int mLogicalSlot; + PrivilegeListener(final int logicalSlot) { mLogicalSlot = logicalSlot; } - @Override public void onCarrierPrivilegesChanged( + @Override + public void onCarrierPrivilegesChanged( @NonNull List privilegedPackageNames, @NonNull int[] privilegedUids) { if (mUseCallbacksForServiceChanged) return; @@ -209,7 +212,9 @@ public class CarrierPrivilegeAuthenticator { public boolean hasCarrierPrivilegeForNetworkCapabilities(int callingUid, @NonNull NetworkCapabilities networkCapabilities) { if (callingUid == Process.INVALID_UID) return false; - if (!networkCapabilities.hasSingleTransport(TRANSPORT_CELLULAR)) return false; + if (!networkCapabilities.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR)) { + return false; + } final int subId = getSubIdFromNetworkSpecifier(networkCapabilities.getNetworkSpecifier()); if (SubscriptionManager.INVALID_SUBSCRIPTION_ID == subId) return false; return callingUid == getCarrierServiceUidForSubId(subId); @@ -292,4 +297,16 @@ public class CarrierPrivilegeAuthenticator { Log.e(TAG, "removeCarrierPrivilegesListener API is not available"); } } + + public void dump(IndentingPrintWriter pw) { + pw.println("CarrierPrivilegeAuthenticator:"); + synchronized (mLock) { + final int size = mCarrierServiceUid.size(); + for (int i = 0; i < size; ++i) { + final int logicalSlot = mCarrierServiceUid.keyAt(i); + final int serviceUid = mCarrierServiceUid.valueAt(i); + pw.println("Logical slot = " + logicalSlot + " : uid = " + serviceUid); + } + } + } } diff --git a/service/src/com/android/server/connectivity/NetworkAgentInfo.java b/service/src/com/android/server/connectivity/NetworkAgentInfo.java index 7cd3cc890c..567fd41f28 100644 --- a/service/src/com/android/server/connectivity/NetworkAgentInfo.java +++ b/service/src/com/android/server/connectivity/NetworkAgentInfo.java @@ -17,6 +17,7 @@ package com.android.server.connectivity; import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; +import static android.net.NetworkCapabilities.NET_CAPABILITY_INTERNET; import static android.net.NetworkCapabilities.NET_CAPABILITY_LOCAL_NETWORK; import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; @@ -1549,7 +1550,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { * @param hasAutomotiveFeature true if this device has the automotive feature, false otherwise * @param authenticator the carrier privilege authenticator to check for telephony constraints */ - public static void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc, + public void restrictCapabilitiesFromNetworkAgent(@NonNull final NetworkCapabilities nc, final int creatorUid, final boolean hasAutomotiveFeature, @NonNull final ConnectivityService.Dependencies deps, @Nullable final CarrierPrivilegeAuthenticator authenticator) { @@ -1562,7 +1563,7 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { } } - private static boolean areAllowedUidsAcceptableFromNetworkAgent( + private boolean areAllowedUidsAcceptableFromNetworkAgent( @NonNull final NetworkCapabilities nc, final boolean hasAutomotiveFeature, @NonNull final ConnectivityService.Dependencies deps, @Nullable final CarrierPrivilegeAuthenticator carrierPrivilegeAuthenticator) { @@ -1575,19 +1576,25 @@ public class NetworkAgentInfo implements NetworkRanker.Scoreable { // On a non-restricted network, access UIDs make no sense if (nc.hasCapability(NET_CAPABILITY_NOT_RESTRICTED)) return false; - // If this network has TRANSPORT_TEST, then the caller can do whatever they want to - // access UIDs - if (nc.hasTransport(TRANSPORT_TEST)) return true; + // If this network has TRANSPORT_TEST and nothing else, then the caller can do whatever + // they want to access UIDs + if (nc.hasSingleTransport(TRANSPORT_TEST)) return true; - // Factories that make ethernet networks can allow UIDs for automotive devices. - if (nc.hasSingleTransport(TRANSPORT_ETHERNET) && hasAutomotiveFeature) { - return true; + if (nc.hasTransport(TRANSPORT_ETHERNET)) { + // Factories that make ethernet networks can allow UIDs for automotive devices. + if (hasAutomotiveFeature) return true; + // It's also admissible if the ethernet network has TRANSPORT_TEST, as long as it + // doesn't have NET_CAPABILITY_INTERNET so it can't become the default network. + if (nc.hasTransport(TRANSPORT_TEST) && !nc.hasCapability(NET_CAPABILITY_INTERNET)) { + return true; + } + return false; } // Factories that make cell networks can allow the UID for the carrier service package. // This can only work in T where there is support for CarrierPrivilegeAuthenticator if (null != carrierPrivilegeAuthenticator - && nc.hasSingleTransport(TRANSPORT_CELLULAR) + && nc.hasSingleTransportBesidesTest(TRANSPORT_CELLULAR) && (1 == nc.getAllowedUidsNoCopy().size()) && (carrierPrivilegeAuthenticator.hasCarrierPrivilegeForNetworkCapabilities( nc.getAllowedUidsNoCopy().valueAt(0), nc))) { diff --git a/tests/cts/net/Android.bp b/tests/cts/net/Android.bp index b86de25e1b..9310888a20 100644 --- a/tests/cts/net/Android.bp +++ b/tests/cts/net/Android.bp @@ -62,10 +62,13 @@ java_defaults { // uncomment when b/13249961 is fixed // sdk_version: "current", platform_apis: true, - data: [":ConnectivityTestPreparer"], per_testcase_directory: true, host_required: ["net-tests-utils-host-common"], test_config_template: "AndroidTestTemplate.xml", + data: [ + ":ConnectivityTestPreparer", + ":CtsCarrierServicePackage", + ] } // Networking CTS tests for development and release. These tests always target the platform SDK @@ -154,3 +157,17 @@ android_test { package_name: "android.net.cts.maxtargetsdk30", // CTS package names must be unique. instrumentation_target_package: "android.net.cts.maxtargetsdk30", } + +android_test_helper_app { + name: "CtsCarrierServicePackage", + defaults: ["cts_defaults"], + package_name: "android.net.cts.carrierservicepackage", + manifest: "carrierservicepackage/AndroidManifest.xml", + srcs: ["carrierservicepackage/src/**/*.java"], + min_sdk_version: "30", + // Tag this module as a cts test artifact + test_suites: [ + "cts", + "general-tests", + ], +} diff --git a/tests/cts/net/AndroidManifest.xml b/tests/cts/net/AndroidManifest.xml index 68e36fff5f..098cc0ae1d 100644 --- a/tests/cts/net/AndroidManifest.xml +++ b/tests/cts/net/AndroidManifest.xml @@ -36,6 +36,7 @@ + + + + + + + + + + + + + + diff --git a/tests/cts/net/carrierservicepackage/src/android/net/cts/carrierservicepackage/DummyCarrierConfigService.java b/tests/cts/net/carrierservicepackage/src/android/net/cts/carrierservicepackage/DummyCarrierConfigService.java new file mode 100644 index 0000000000..ca2015bb4d --- /dev/null +++ b/tests/cts/net/carrierservicepackage/src/android/net/cts/carrierservicepackage/DummyCarrierConfigService.java @@ -0,0 +1,45 @@ +/* + * Copyright (C) 2023 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 android.net.cts.carrierservicepackage; + +import android.content.Intent; +import android.os.IBinder; +import android.os.PersistableBundle; +import android.service.carrier.CarrierIdentifier; +import android.service.carrier.CarrierService; + +public class DummyCarrierConfigService extends CarrierService { + private static final String TAG = "DummyCarrierConfigService"; + + public DummyCarrierConfigService() {} + + @Override + public PersistableBundle onLoadConfig(CarrierIdentifier id) { + return new PersistableBundle(); // Do nothing + } + + @Override + public IBinder onBind(Intent intent) { + return super.onBind(intent); + } + + @Override + public boolean onUnbind(Intent intent) { + return super.onUnbind(intent); + } +} + diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java index 0a143c538a..778f0c5429 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java @@ -796,14 +796,15 @@ public class ConnectivityManagerTest { // Make sure that the NC is null if the package doesn't hold ACCESS_NETWORK_STATE. assertNull(redactNc(nc, groundedUid, groundedPkg)); - // Uids, ssid, underlying networks & subscriptionIds will be redacted if the given uid + // Uids, ssid & underlying networks will be redacted if the given uid // doesn't hold the associated permissions. The wifi transport info is also suitably // redacted. final NetworkCapabilities redactedNormal = redactNc(nc, normalUid, normalPkg); assertNull(redactedNormal.getUids()); assertNull(redactedNormal.getSsid()); assertNull(redactedNormal.getUnderlyingNetworks()); - assertEquals(0, redactedNormal.getSubscriptionIds().size()); + // Owner UID is allowed to see the subscription IDs. + assertEquals(2, redactedNormal.getSubscriptionIds().size()); assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, ((WifiInfo) redactedNormal.getTransportInfo()).getBSSID()); assertEquals(rssi, ((WifiInfo) redactedNormal.getTransportInfo()).getRssi()); diff --git a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt index 392cba9bb8..3b6607f5b3 100644 --- a/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt +++ b/tests/cts/net/src/android/net/cts/NetworkAgentTest.kt @@ -15,9 +15,12 @@ */ package android.net.cts +import android.Manifest.permission.MODIFY_PHONE_STATE import android.Manifest.permission.NETWORK_SETTINGS +import android.Manifest.permission.READ_PRIVILEGED_PHONE_STATE import android.app.Instrumentation import android.content.Context +import android.content.pm.PackageManager import android.net.ConnectivityManager import android.net.EthernetNetworkSpecifier import android.net.INetworkAgent @@ -44,7 +47,9 @@ 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_BLUETOOTH import android.net.NetworkCapabilities.TRANSPORT_CELLULAR +import android.net.NetworkCapabilities.TRANSPORT_ETHERNET import android.net.NetworkCapabilities.TRANSPORT_TEST import android.net.NetworkCapabilities.TRANSPORT_VPN import android.net.NetworkCapabilities.TRANSPORT_WIFI @@ -53,6 +58,7 @@ import android.net.NetworkProvider import android.net.NetworkReleasedException import android.net.NetworkRequest import android.net.NetworkScore +import android.net.NetworkSpecifier import android.net.QosCallback import android.net.QosCallback.QosCallbackRegistrationException import android.net.QosCallbackException @@ -61,6 +67,7 @@ import android.net.QosSessionAttributes import android.net.QosSocketInfo import android.net.RouteInfo import android.net.SocketKeepalive +import android.net.TelephonyNetworkSpecifier import android.net.TestNetworkInterface import android.net.TestNetworkManager import android.net.Uri @@ -69,21 +76,31 @@ import android.net.VpnTransportInfo import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnError import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionAvailable import android.net.cts.NetworkAgentTest.TestableQosCallback.CallbackEntry.OnQosSessionLost +import android.net.wifi.WifiInfo import android.os.Build +import android.os.ConditionVariable import android.os.Handler import android.os.HandlerThread import android.os.Message +import android.os.PersistableBundle import android.os.Process import android.os.SystemClock import android.platform.test.annotations.AppModeFull import android.system.OsConstants.IPPROTO_TCP import android.system.OsConstants.IPPROTO_UDP +import android.telephony.CarrierConfigManager +import android.telephony.SubscriptionManager import android.telephony.TelephonyManager +import android.telephony.TelephonyManager.CarrierPrivilegesCallback import android.telephony.data.EpsBearerQosSessionAttributes +import android.util.ArraySet import android.util.DebugUtils.valueToString +import android.util.Log import androidx.test.InstrumentationRegistry +import com.android.compatibility.common.util.SystemUtil.runShellCommand import com.android.compatibility.common.util.SystemUtil.runWithShellPermissionIdentity import com.android.compatibility.common.util.ThrowingSupplier +import com.android.compatibility.common.util.UiccUtil import com.android.modules.utils.build.SdkLevel import com.android.net.module.util.ArrayTrackRecord import com.android.testutils.CompatUtil @@ -112,23 +129,8 @@ import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnUnregisterQosC import com.android.testutils.TestableNetworkAgent.CallbackEntry.OnValidationStatus import com.android.testutils.TestableNetworkCallback import com.android.testutils.assertThrows -import java.io.Closeable -import java.io.IOException -import java.net.DatagramSocket -import java.net.InetAddress -import java.net.InetSocketAddress -import java.net.Socket -import java.time.Duration -import java.util.Arrays -import java.util.UUID -import java.util.concurrent.Executors -import kotlin.test.assertEquals -import kotlin.test.assertFailsWith -import kotlin.test.assertFalse -import kotlin.test.assertNotNull -import kotlin.test.assertNull -import kotlin.test.assertTrue -import kotlin.test.fail +import com.android.testutils.runAsShell +import com.android.testutils.tryTest import org.junit.After import org.junit.Before import org.junit.Test @@ -140,7 +142,26 @@ import org.mockito.Mockito.doReturn import org.mockito.Mockito.mock import org.mockito.Mockito.timeout import org.mockito.Mockito.verify +import java.io.Closeable +import java.io.IOException +import java.net.DatagramSocket +import java.net.InetAddress +import java.net.InetSocketAddress +import java.net.Socket +import java.security.MessageDigest +import java.time.Duration +import java.util.Arrays +import java.util.UUID +import java.util.concurrent.Executors +import kotlin.test.assertEquals +import kotlin.test.assertFailsWith +import kotlin.test.assertFalse +import kotlin.test.assertNotNull +import kotlin.test.assertNull +import kotlin.test.assertTrue +import kotlin.test.fail +private const val TAG = "NetworkAgentTest" // This test doesn't really have a constraint on how fast the methods should return. If it's // going to fail, it will simply wait forever, so setting a high timeout lowers the flake ratio // without affecting the run time of successful runs. Thus, set a very high timeout. @@ -261,17 +282,18 @@ class NetworkAgentTest { callbacksToCleanUp.add(callback) } - private fun makeTestNetworkRequest(specifier: String? = null): NetworkRequest { - return NetworkRequest.Builder() - .clearCapabilities() - .addTransportType(TRANSPORT_TEST) - .also { - if (specifier != null) { - it.setNetworkSpecifier(CompatUtil.makeEthernetNetworkSpecifier(specifier)) - } - } - .build() - } + private fun String?.asEthSpecifier(): NetworkSpecifier? = + if (null == this) null else CompatUtil.makeEthernetNetworkSpecifier(this) + private fun makeTestNetworkRequest(specifier: NetworkSpecifier? = null) = + NetworkRequest.Builder().run { + clearCapabilities() + addTransportType(TRANSPORT_TEST) + if (specifier != null) setNetworkSpecifier(specifier) + build() + } + + private fun makeTestNetworkRequest(specifier: String?) = + makeTestNetworkRequest(specifier.asEthSpecifier()) private fun makeTestNetworkCapabilities( specifier: String? = null, @@ -322,7 +344,7 @@ class NetworkAgentTest { ): Pair { val callback = TestableNetworkCallback() // Ensure this NetworkAgent is never unneeded by filing a request with its specifier. - requestNetwork(makeTestNetworkRequest(specifier = specifier), callback) + requestNetwork(makeTestNetworkRequest(specifier), callback) val nc = makeTestNetworkCapabilities(specifier, transports) val agent = createNetworkAgent(context, initialConfig = initialConfig, initialNc = nc) agent.setTeardownDelayMillis(0) @@ -543,6 +565,227 @@ class NetworkAgentTest { .addTransportType(TRANSPORT_TEST) .setAllowedUids(uids.toSet()).build() + /** + * Get the single element from this ArraySet, or fail() if doesn't contain exactly 1 element. + */ + fun ArraySet.getSingleElement(): T { + if (size != 1) fail("Expected exactly one element, contained $size") + return iterator().next() + } + + private fun doTestAllowedUids( + subId: Int, + transport: Int, + uid: Int, + expectUidsPresent: Boolean + ) { + doTestAllowedUids(subId, intArrayOf(transport), uid, expectUidsPresent) + } + + private fun doTestAllowedUids( + subId: Int, + transports: IntArray, + uid: Int, + expectUidsPresent: Boolean + ) { + val callback = TestableNetworkCallback(DEFAULT_TIMEOUT_MS) + val specifier = when { + transports.size != 1 -> null + TRANSPORT_ETHERNET in transports -> EthernetNetworkSpecifier("testInterface") + TRANSPORT_CELLULAR in transports -> TelephonyNetworkSpecifier(subId) + else -> null + } + val agent = createNetworkAgent(initialNc = NetworkCapabilities.Builder().run { + addTransportType(TRANSPORT_TEST) + transports.forEach { addTransportType(it) } + addCapability(NET_CAPABILITY_NOT_VCN_MANAGED) + addCapability(NET_CAPABILITY_NOT_SUSPENDED) + removeCapability(NET_CAPABILITY_NOT_RESTRICTED) + setNetworkSpecifier(specifier) + if (TRANSPORT_WIFI in transports && SdkLevel.isAtLeastV()) { + // setSubscriptionId only exists in V+ + setTransportInfo(WifiInfo.Builder().setSubscriptionId(subId).build()) + } + setAllowedUids(setOf(uid)) + setOwnerUid(Process.myUid()) + setAdministratorUids(intArrayOf(Process.myUid())) + build() + }) + runWithShellPermissionIdentity { + agent.register() + } + agent.markConnected() + + registerNetworkCallback(makeTestNetworkRequest(specifier), callback) + callback.expect(agent.network!!) + callback.expect(agent.network!!) { + if (expectUidsPresent) { + it.caps.allowedUidsNoCopy.getSingleElement() == uid + } else { + it.caps.allowedUidsNoCopy.isEmpty() + } + } + agent.unregister() + // callback will be unregistered in tearDown() + } + + private fun setHoldCarrierPrivilege(hold: Boolean, subId: Int) { + fun getCertHash(): String { + val pkgInfo = realContext.packageManager.getPackageInfo(realContext.opPackageName, + PackageManager.GET_SIGNATURES) + val digest = MessageDigest.getInstance("SHA-256") + val certHash = digest.digest(pkgInfo.signatures!![0]!!.toByteArray()) + return UiccUtil.bytesToHexString(certHash)!! + } + + val tm = realContext.getSystemService(TelephonyManager::class.java)!! + val ccm = realContext.getSystemService(CarrierConfigManager::class.java)!! + + val cv = ConditionVariable() + val cpb = PrivilegeWaiterCallback(cv) + tryTest { + val slotIndex = SubscriptionManager.getSlotIndex(subId)!! + runAsShell(READ_PRIVILEGED_PHONE_STATE) { + tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb) + } + // Wait for the callback to be registered + assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback") + if (cpb.hasPrivilege == hold) { + if (hold) { + Log.w(TAG, "Package ${realContext.opPackageName} already is privileged") + } else { + Log.w(TAG, "Package ${realContext.opPackageName} already isn't privileged") + } + return@tryTest + } + cv.close() + runAsShell(MODIFY_PHONE_STATE) { + val carrierConfigs = if (hold) { + PersistableBundle().also { + it.putStringArray(CarrierConfigManager.KEY_CARRIER_CERTIFICATE_STRING_ARRAY, + arrayOf(getCertHash())) + } + } else { + null + } + ccm.overrideConfig(subId, carrierConfigs) + } + assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't change carrier privilege") + } cleanup { + runAsShell(READ_PRIVILEGED_PHONE_STATE) { + tm.unregisterCarrierPrivilegesCallback(cpb) + } + } + } + + private fun acquireCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(true, subId) + private fun dropCarrierPrivilege(subId: Int) = setHoldCarrierPrivilege(false, subId) + + private fun setCarrierServicePackageOverride(subId: Int, pkg: String?) { + val tm = realContext.getSystemService(TelephonyManager::class.java)!! + + val cv = ConditionVariable() + val cpb = CarrierServiceChangedWaiterCallback(cv) + tryTest { + val slotIndex = SubscriptionManager.getSlotIndex(subId)!! + runAsShell(READ_PRIVILEGED_PHONE_STATE) { + tm.registerCarrierPrivilegesCallback(slotIndex, { it.run() }, cpb) + } + // Wait for the callback to be registered + assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't register CarrierPrivilegesCallback") + if (cpb.pkgName == pkg) { + Log.w(TAG, "Carrier service package was already $pkg") + return@tryTest + } + cv.close() + runAsShell(MODIFY_PHONE_STATE) { + if (null == pkg) { + // There is a bug is clear-carrier-service-package-override where not adding + // the -s argument will use the wrong slot index : b/299604822 + runShellCommand("cmd phone clear-carrier-service-package-override" + + " -s $subId") + } else { + // -s could set the subId, but this test works with the default subId. + runShellCommand("cmd phone set-carrier-service-package-override $pkg") + } + } + assertTrue(cv.block(DEFAULT_TIMEOUT_MS), "Can't modify carrier service package") + } cleanup { + runAsShell(READ_PRIVILEGED_PHONE_STATE) { + tm.unregisterCarrierPrivilegesCallback(cpb) + } + } + } + + private fun String.execute() = runShellCommand(this).trim() + + @Test + @IgnoreUpTo(Build.VERSION_CODES.S) + fun testAllowedUids() { + // Use a different package than this one to make sure that a package that doesn't hold + // carrier service permission can be set as an allowed UID. + val servicePackage = "android.net.cts.carrierservicepackage" + val uid = try { + realContext.packageManager.getApplicationInfo(servicePackage, 0).uid + } catch (e: PackageManager.NameNotFoundException) { + fail("$servicePackage could not be installed, please check the SuiteApkInstaller" + + " installed CtsCarrierServicePackage.apk", e) + } + + val tm = realContext.getSystemService(TelephonyManager::class.java)!! + val defaultSubId = SubscriptionManager.getDefaultSubscriptionId() + tryTest { + // This process is not the carrier service UID, so allowedUids should be ignored in all + // the following cases. + doTestAllowedUids(defaultSubId, TRANSPORT_CELLULAR, uid, expectUidsPresent = false) + doTestAllowedUids(defaultSubId, TRANSPORT_WIFI, uid, expectUidsPresent = false) + doTestAllowedUids(defaultSubId, TRANSPORT_BLUETOOTH, uid, expectUidsPresent = false) + + // The tools to set the carrier service package override do not exist before U, + // so there is no way to test the rest of this test on < U. + if (!SdkLevel.isAtLeastU()) return@tryTest + // Acquiring carrier privilege is necessary to override the carrier service package. + val defaultSlotIndex = SubscriptionManager.getSlotIndex(defaultSubId) + acquireCarrierPrivilege(defaultSubId) + setCarrierServicePackageOverride(defaultSubId, servicePackage) + val actualServicePackage: String? = runAsShell(READ_PRIVILEGED_PHONE_STATE) { + tm.getCarrierServicePackageNameForLogicalSlot(defaultSlotIndex) + } + assertEquals(servicePackage, actualServicePackage) + + // Wait for CarrierServiceAuthenticator to have seen the update of the service package + val timeout = SystemClock.elapsedRealtime() + DEFAULT_TIMEOUT_MS + while (true) { + if (SystemClock.elapsedRealtime() > timeout) { + fail("Couldn't make $servicePackage the service package for $defaultSubId: " + + "dumpsys connectivity".execute().split("\n") + .filter { it.contains("Logical slot = $defaultSlotIndex.*") }) + } + if ("dumpsys connectivity" + .execute() + .split("\n") + .filter { it.contains("Logical slot = $defaultSlotIndex : uid = $uid") } + .isNotEmpty()) { + // Found the configuration + break + } + Thread.sleep(500) + } + + // Cell is allowed to set UIDs, but not WIFI/BLUETOOTH or agents with multiple + // transports. + doTestAllowedUids(defaultSubId, TRANSPORT_CELLULAR, uid, expectUidsPresent = true) + doTestAllowedUids(defaultSubId, TRANSPORT_WIFI, uid, expectUidsPresent = false) + doTestAllowedUids(defaultSubId, TRANSPORT_BLUETOOTH, uid, expectUidsPresent = false) + doTestAllowedUids(defaultSubId, intArrayOf(TRANSPORT_CELLULAR, TRANSPORT_WIFI), uid, + expectUidsPresent = false) + } cleanupStep { + if (SdkLevel.isAtLeastU()) setCarrierServicePackageOverride(defaultSubId, null) + } cleanup { + if (SdkLevel.isAtLeastU()) dropCarrierPrivilege(defaultSubId) + } + } + @Test fun testRejectedUpdates() { val callback = TestableNetworkCallback(DEFAULT_TIMEOUT_MS) @@ -1497,3 +1740,25 @@ class NetworkAgentTest { doTestNativeNetworkCreation(expectCreatedImmediately = true, intArrayOf(TRANSPORT_VPN)) } } + +// Subclasses of CarrierPrivilegesCallback can't be inline, or they'll be compiled as +// inner classes of the test class and will fail resolution on R as the test harness +// uses reflection to list all methods and classes +class PrivilegeWaiterCallback(private val cv: ConditionVariable) : + CarrierPrivilegesCallback { + var hasPrivilege = false + override fun onCarrierPrivilegesChanged(p: MutableSet, uids: MutableSet) { + hasPrivilege = uids.contains(Process.myUid()) + cv.open() + } +} + +class CarrierServiceChangedWaiterCallback(private val cv: ConditionVariable) : + CarrierPrivilegesCallback { + var pkgName: String? = null + override fun onCarrierPrivilegesChanged(p: MutableSet, u: MutableSet) {} + override fun onCarrierServiceChanged(pkgName: String?, uid: Int) { + this.pkgName = pkgName + cv.open() + } +} diff --git a/tests/unit/java/com/android/server/ConnectivityServiceTest.java b/tests/unit/java/com/android/server/ConnectivityServiceTest.java index b8cf08e609..b076fe57f1 100755 --- a/tests/unit/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/unit/java/com/android/server/ConnectivityServiceTest.java @@ -17386,6 +17386,7 @@ public class ConnectivityServiceTest { mCm.requestNetwork(new NetworkRequest.Builder() .clearCapabilities() .addTransportType(TRANSPORT_TEST) + .addTransportType(TRANSPORT_CELLULAR) .build(), cb);