diff --git a/framework/src/android/net/NetworkCapabilities.java b/framework/src/android/net/NetworkCapabilities.java index 9b099e42b8..5974b58c9e 100644 --- a/framework/src/android/net/NetworkCapabilities.java +++ b/framework/src/android/net/NetworkCapabilities.java @@ -19,6 +19,8 @@ package android.net; import static com.android.internal.annotations.VisibleForTesting.Visibility.PRIVATE; import static com.android.net.module.util.BitUtils.appendStringRepresentationOfBitMaskToStringBuilder; import static com.android.net.module.util.BitUtils.describeDifferences; +import static com.android.net.module.util.FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED; +import static com.android.net.module.util.FrameworkConnectivityStatsLog.CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_SPECIFIER_REDACTION_REFLECTION_ERROR; import android.annotation.FlaggedApi; import android.annotation.IntDef; @@ -37,6 +39,7 @@ import android.os.Process; import android.text.TextUtils; import android.util.ArraySet; import android.util.Log; +import android.util.LruCache; import android.util.Range; import com.android.internal.annotations.VisibleForTesting; @@ -44,15 +47,19 @@ import com.android.modules.utils.build.SdkLevel; import com.android.net.flags.Flags; import com.android.net.module.util.BitUtils; import com.android.net.module.util.CollectionUtils; +import com.android.net.module.util.FrameworkConnectivityStatsLog; import com.android.net.module.util.NetworkCapabilitiesUtils; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; +import java.lang.reflect.InvocationTargetException; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; import java.util.Objects; +import java.util.Optional; import java.util.Set; import java.util.StringJoiner; @@ -3519,4 +3526,148 @@ public final class NetworkCapabilities implements Parcelable { return new NetworkCapabilities(mCaps); } } + + /** + * Helper class for providing access to {@link NetworkSpecifier} redaction methods via + * reflection. + * + * Reflection is necessary because NetworkSpecifier is platform code. So it's possible for a + * mainline update to define a getApplicableRedactions() or redact(long) method in a subclass + * of NetworkSpecifier (e.g., EthernetNetworkSpecifier), and then to run that mainline module + * on an Android release where the NetworkSpecifier base class does not have those methods. + * On such a device, ConnectivityService must still call those methods if they exist, otherwise + * it would fail to redact information that needs to be redacted. Another way to do this would + * be for ConnectivityService to just call the method and catch NoSuchMethodError, but this + * code runs fairly often and constructing exceptions is expensive. + * + * @hide + */ + public static final class RedactionHelper { + // LruCache is thread-safe so that no locks are used to project it this class + private static final LruCache, Optional> + sGetApplicableRedactionsMethodCache = new LruCache<>(100); + private static final LruCache, Optional> + sRedactLongMethodCache = new LruCache<>(100); + + private RedactionHelper() {} + + /** + * Returns the {@link Method} of the {@link NetworkSpecifier} class for the given method + * name, or {@code null} if the method doesn't exist. + *

+ * This is typically used for {@link NetworkSpecifier#getApplicableRedactions()} and {@link + * NetworkSpecifier#redact(long)} to work with new NetworkSpecifier subclasses added in new + * mainline version but deployed to older platforms which don't have those two + * NetworkSpecifier methods. + *

+ * See {@link #createWithLocationInfoSanitizedIfNecessaryWhenParceled} and {@link + * #ensureSufficientPermissionsForRequest} for how this method should be used. + */ + @Nullable + private static Method getNetworkSpecifierMethod( + Class clazz, String name, Class returnType, + Class... parameterTypes) { + // Use {@code getMethods()} rather than {@code getDeclaredMethods()} so that it can + // return the inherited NetworkSpecifier methods for those existing subclasses which + // don't override {@link NetworkSpecifier#getApplicableRedactions()} and {@link + // NetworkSpecifier#redact(long)}. + // Note also {@code getMethods()} returns only public methods, which is necessary for + // code to call the method later. + for (Method method : clazz.getMethods()) { + if (name.equals(method.getName()) + && returnType.equals(method.getReturnType()) + && Arrays.equals(parameterTypes, method.getParameterTypes())) { + return method; + } + } + return null; + } + + /** + * Returns the {@link NetworkSpecifier#getApplicableRedactions()} method or {@code null} if + * it doesn't exist for the given {@link NetworkSpecifier} subclass. + */ + @Nullable + private static Method getApplicableRedactionsMethod( + Class clazz) { + // If this method is called concurrently from two threads, both may see cache missed + // and the reflection branch in method will be executed for twice. But this is okay + // because getApplicableRedactions() itself has no side-effect (and this is why we can + // create caches for it). + Optional cache = sGetApplicableRedactionsMethodCache.get(clazz); + if (cache != null) { + return cache.orElse(null); + } + Method method = getNetworkSpecifierMethod( + clazz, "getApplicableRedactions", long.class); + sGetApplicableRedactionsMethodCache.put(clazz, Optional.ofNullable(method)); + return method; + } + + /** + * Returns the value of {@link NetworkSpecifier#getApplicableRedactions()} or + * {@link REDACT_NONE} if the platform doesn't support + * {@link NetworkSpecifier#getApplicableRedactions()}. + */ + public static long getSpecifierApplicableRedactions(NetworkSpecifier specifier) { + final Method getApplicableRedactions = + getApplicableRedactionsMethod(specifier.getClass()); + if (getApplicableRedactions == null) { + return REDACT_NONE; + } + + try { + return (long) getApplicableRedactions.invoke(specifier); + } catch (IllegalAccessException | InvocationTargetException + | IllegalArgumentException e) { + FrameworkConnectivityStatsLog.write(CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, + CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_SPECIFIER_REDACTION_REFLECTION_ERROR); + Log.wtf(TAG, "Un expected reflection error on specifier " + specifier, e); + return REDACT_ALL; + } + } + + /** + * Returns the {@link NetworkSpecifier#redact(long)} method or {@code null} if it doesn't + * exist for the given {@link NetworkSpecifier} subclass. + */ + @Nullable + private static Method getRedactLongMethod(Class clazz) { + // If this method is called concurrently from two threads, both may see cache missed + // and the reflection branch in method will be executed for twice. But this is okay + // because redact(long) itself has no side-effect (and this is why we can create caches + // for it). + Optional cache = sRedactLongMethodCache.get(clazz); + if (cache != null) { + return cache.orElse(null); + } + Method method = getNetworkSpecifierMethod( + clazz, "redact", NetworkSpecifier.class, long.class); + sRedactLongMethodCache.put(clazz, Optional.ofNullable(method)); + return method; + } + + /** + * Returns the result of {@link NetworkSpecifier#redact(long)} or {@code specifier} itself + * if the platform doesn't support {@link NetworkSpecifier#redact(long)}. + */ + @Nullable + public static NetworkSpecifier getSpecifierRedactResult( + NetworkSpecifier specifier, long redactions) { + final Method redactLong = getRedactLongMethod(specifier.getClass()); + if (redactLong == null) { + return specifier; + } + + try { + return (NetworkSpecifier) redactLong.invoke(specifier, redactions); + } catch (IllegalAccessException | InvocationTargetException + | IllegalArgumentException e) { + FrameworkConnectivityStatsLog.write(CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED, + CORE_NETWORKING_TERRIBLE_ERROR_OCCURRED__ERROR_TYPE__TYPE_SPECIFIER_REDACTION_REFLECTION_ERROR); + Log.wtf(TAG, "Un expected reflection error on specifier " + specifier, e); + return null; + } + } + } } diff --git a/tests/common/java/android/net/NetworkCapabilitiesTest.java b/tests/common/java/android/net/NetworkCapabilitiesTest.java index 499b52d3be..4f6dbb0f1d 100644 --- a/tests/common/java/android/net/NetworkCapabilitiesTest.java +++ b/tests/common/java/android/net/NetworkCapabilitiesTest.java @@ -51,6 +51,8 @@ import static android.net.NetworkCapabilities.NET_ENTERPRISE_ID_5; import static android.net.NetworkCapabilities.REDACT_FOR_ACCESS_FINE_LOCATION; import static android.net.NetworkCapabilities.REDACT_FOR_LOCAL_MAC_ADDRESS; import static android.net.NetworkCapabilities.REDACT_FOR_NETWORK_SETTINGS; +import static android.net.NetworkCapabilities.REDACT_FOR_THREAD_NETWORK_PRIVILEGED; +import static android.net.NetworkCapabilities.REDACT_NONE; import static android.net.NetworkCapabilities.SIGNAL_STRENGTH_UNSPECIFIED; import static android.net.NetworkCapabilities.TRANSPORT_CELLULAR; import static android.net.NetworkCapabilities.TRANSPORT_ETHERNET; @@ -63,9 +65,11 @@ import static android.net.NetworkCapabilities.TRANSPORT_WIFI; import static android.net.NetworkCapabilities.TRANSPORT_WIFI_AWARE; import static android.os.Process.INVALID_UID; +import static com.android.modules.utils.build.SdkLevel.isAtLeastB; import static com.android.modules.utils.build.SdkLevel.isAtLeastS; import static com.android.modules.utils.build.SdkLevel.isAtLeastT; import static com.android.modules.utils.build.SdkLevel.isAtLeastV; +import static com.android.net.thread.platform.flags.Flags.FLAG_THREAD_MOBILE_ENABLED; import static com.android.testutils.MiscAsserts.assertEmpty; import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.ParcelUtils.assertParcelingIsLossless; @@ -74,11 +78,15 @@ import static org.junit.Assert.assertArrayEquals; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertNotEquals; +import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +import static org.junit.Assume.assumeFalse; import static org.junit.Assume.assumeTrue; +import android.annotation.Nullable; +import android.net.NetworkCapabilities.RedactionHelper; import android.net.wifi.aware.DiscoverySession; import android.net.wifi.aware.PeerHandle; import android.net.wifi.aware.WifiAwareNetworkSpecifier; @@ -99,6 +107,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.List; @@ -1235,6 +1244,44 @@ public class NetworkCapabilitiesTest { } } + /** + * Redactable NetworkSpecifier subclass for verifying redaction mechanism. + */ + private static final class RedactableNetworkSpecifier extends NetworkSpecifier { + final boolean locationRedacted; + final boolean threadNetworkRedacted; + + RedactableNetworkSpecifier() { + locationRedacted = false; + threadNetworkRedacted = false; + } + + RedactableNetworkSpecifier(boolean locationRedacted, boolean threadNetworkRedacted) { + this.locationRedacted = locationRedacted; + this.threadNetworkRedacted = threadNetworkRedacted; + } + + @Override + public long getApplicableRedactions() { + return REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_THREAD_NETWORK_PRIVILEGED; + } + + @Override + @Nullable + public NetworkSpecifier redact(long redactions) { + return new RedactableNetworkSpecifier( + (redactions & REDACT_FOR_ACCESS_FINE_LOCATION) != 0, + (redactions & REDACT_FOR_THREAD_NETWORK_PRIVILEGED) != 0); + } + } + + /** + * Unredactable NetworkSpecifier subclass for verifying redaction mechanism. + */ + private static final class UnredactableNetworkSpecifier extends NetworkSpecifier { + // not overriding the getApplicableRedactions() and redact(long) methods + } + @Test public void testBuilder() { final int ownerUid = 1001; @@ -1621,4 +1668,35 @@ public class NetworkCapabilitiesTest { assertFalse(wifiRequestNc.satisfiedByNetworkCapabilities(localWifiNc)); } } + + @Test + public void testRedactionHelper() throws Exception { + final UnredactableNetworkSpecifier unredactableSpecifier = + new UnredactableNetworkSpecifier(); + final RedactableNetworkSpecifier redactableSpecifier = new RedactableNetworkSpecifier(); + + assertEquals(REDACT_NONE, + RedactionHelper.getSpecifierApplicableRedactions(unredactableSpecifier)); + assertEquals(unredactableSpecifier, + RedactionHelper.getSpecifierRedactResult( + unredactableSpecifier, REDACT_FOR_ACCESS_FINE_LOCATION)); + assertEquals(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_THREAD_NETWORK_PRIVILEGED, + RedactionHelper.getSpecifierApplicableRedactions(redactableSpecifier)); + // Call getSpecifierApplicableRedactions() twice to make sure the cache works + assertEquals(REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_THREAD_NETWORK_PRIVILEGED, + RedactionHelper.getSpecifierApplicableRedactions(redactableSpecifier)); + final RedactableNetworkSpecifier redactedSpecifier = + (RedactableNetworkSpecifier) RedactionHelper.getSpecifierRedactResult( + redactableSpecifier, + REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_THREAD_NETWORK_PRIVILEGED); + assertTrue(redactedSpecifier.locationRedacted); + assertTrue(redactedSpecifier.threadNetworkRedacted); + // Call getSpecifierRedactResult twice to make sure the cache works + final RedactableNetworkSpecifier redactedSpecifier2 = + (RedactableNetworkSpecifier) RedactionHelper.getSpecifierRedactResult( + redactableSpecifier, + REDACT_FOR_ACCESS_FINE_LOCATION | REDACT_FOR_THREAD_NETWORK_PRIVILEGED); + assertTrue(redactedSpecifier2.locationRedacted); + assertTrue(redactedSpecifier2.threadNetworkRedacted); + } }