diff --git a/framework/api/module-lib-current.txt b/framework/api/module-lib-current.txt index 64e159b255..dec9d35b9c 100644 --- a/framework/api/module-lib-current.txt +++ b/framework/api/module-lib-current.txt @@ -10,6 +10,8 @@ package android.net { method @NonNull @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public java.util.List getAllNetworkStateSnapshots(); method @Nullable public android.net.ProxyInfo getGlobalProxy(); method @NonNull public static android.util.Range getIpSecNetIdRange(); + method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.LinkProperties redactLinkPropertiesForPackage(@NonNull android.net.LinkProperties, int, @NonNull String); + method @Nullable @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public android.net.NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull android.net.NetworkCapabilities, int, @NonNull String); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerDefaultNetworkCallbackForUid(int, @NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, android.Manifest.permission.NETWORK_SETTINGS}) public void registerSystemDefaultNetworkCallback(@NonNull android.net.ConnectivityManager.NetworkCallback, @NonNull android.os.Handler); method @RequiresPermission(anyOf={android.Manifest.permission.NETWORK_SETTINGS, android.Manifest.permission.NETWORK_STACK, android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK}) public void replaceFirewallChain(int, @NonNull int[]); diff --git a/framework/src/android/net/ConnectivityManager.java b/framework/src/android/net/ConnectivityManager.java index 524662336c..e8e1efa09f 100644 --- a/framework/src/android/net/ConnectivityManager.java +++ b/framework/src/android/net/ConnectivityManager.java @@ -1625,16 +1625,45 @@ public class ConnectivityManager { } /** - * Get the {@link NetworkCapabilities} for the given {@link Network}. This - * will return {@code null} if the network is unknown or if the |network| argument is null. + * Redact {@link LinkProperties} for a given package * - * This will remove any location sensitive data in {@link TransportInfo} embedded in - * {@link NetworkCapabilities#getTransportInfo()}. Some transport info instances like - * {@link android.net.wifi.WifiInfo} contain location sensitive information. Retrieving - * this location sensitive information (subject to app's location permissions) will be - * noted by system. To include any location sensitive data in {@link TransportInfo}, - * use a {@link NetworkCallback} with - * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag. + * Returns an instance of the given {@link LinkProperties} appropriately redacted to send to the + * given package, considering its permissions. + * + * @param lp A {@link LinkProperties} which will be redacted. + * @param uid The target uid. + * @param packageName The name of the package, for appops logging. + * @return A redacted {@link LinkProperties} which is appropriate to send to the given uid, + * or null if the uid lacks the ACCESS_NETWORK_STATE permission. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) + @Nullable + public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid, + @NonNull String packageName) { + try { + return mService.redactLinkPropertiesForPackage( + lp, uid, packageName, getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Get the {@link NetworkCapabilities} for the given {@link Network}, or null. + * + * This will remove any location sensitive data in the returned {@link NetworkCapabilities}. + * Some {@link TransportInfo} instances like {@link android.net.wifi.WifiInfo} contain location + * sensitive information. To retrieve this location sensitive information (subject to + * the caller's location permissions), use a {@link NetworkCallback} with the + * {@link NetworkCallback#FLAG_INCLUDE_LOCATION_INFO} flag instead. + * + * This method returns {@code null} if the network is unknown or if the |network| argument + * is null. * * @param network The {@link Network} object identifying the network in question. * @return The {@link NetworkCapabilities} for the network, or {@code null}. @@ -1650,6 +1679,38 @@ public class ConnectivityManager { } } + /** + * Redact {@link NetworkCapabilities} for a given package. + * + * Returns an instance of {@link NetworkCapabilities} that is appropriately redacted to send + * to the given package, considering its permissions. Calling this method will blame the UID for + * retrieving the device location if the passed capabilities contain location-sensitive + * information. + * + * @param nc A {@link NetworkCapabilities} instance which will be redacted. + * @param uid The target uid. + * @param packageName The name of the package, for appops logging. + * @return A redacted {@link NetworkCapabilities} which is appropriate to send to the given uid, + * or null if the uid lacks the ACCESS_NETWORK_STATE permission. + * @hide + */ + @RequiresPermission(anyOf = { + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK, + android.Manifest.permission.NETWORK_STACK, + android.Manifest.permission.NETWORK_SETTINGS}) + @SystemApi(client = MODULE_LIBRARIES) + @Nullable + public NetworkCapabilities redactNetworkCapabilitiesForPackage( + @NonNull NetworkCapabilities nc, + int uid, @NonNull String packageName) { + try { + return mService.redactNetworkCapabilitiesForPackage(nc, uid, packageName, + getAttributionTag()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Gets a URL that can be used for resolving whether a captive portal is present. * 1. This URL should respond with a 204 response to a GET request to indicate no captive @@ -3547,7 +3608,20 @@ public class ConnectivityManager { * @hide */ public static final int FLAG_NONE = 0; + /** + * Inclusion of this flag means location-sensitive redaction requests keeping location info. + * + * Some objects like {@link NetworkCapabilities} may contain location-sensitive information. + * Prior to Android 12, this information is always returned to apps holding the appropriate + * permission, possibly noting that the app has used location. + *

In Android 12 and above, by default the sent objects do not contain any location + * information, even if the app holds the necessary permissions, and the system does not + * take note of location usage by the app. Apps can request that location information is + * included, in which case the system will check location permission and the location + * toggle state, and take note of location usage by the app if any such information is + * returned. + * * Use this flag to include any location sensitive data in {@link NetworkCapabilities} sent * via {@link #onCapabilitiesChanged(Network, NetworkCapabilities)}. *

@@ -3564,8 +3638,7 @@ public class ConnectivityManager { *

  • Retrieving this location sensitive information (subject to app's location * permissions) will be noted by system.
  • *
  • Without this flag any {@link NetworkCapabilities} provided via the callback does - * not include location sensitive info. - *

    + * not include location sensitive information. */ // Note: Some existing fields which are location sensitive may still be included without // this flag if the app targets SDK < S (to maintain backwards compatibility). diff --git a/framework/src/android/net/IConnectivityManager.aidl b/framework/src/android/net/IConnectivityManager.aidl index df4663f663..23a38506b4 100644 --- a/framework/src/android/net/IConnectivityManager.aidl +++ b/framework/src/android/net/IConnectivityManager.aidl @@ -76,10 +76,15 @@ interface IConnectivityManager LinkProperties getActiveLinkProperties(); LinkProperties getLinkPropertiesForType(int networkType); LinkProperties getLinkProperties(in Network network); + LinkProperties redactLinkPropertiesForPackage(in LinkProperties lp, int uid, String packageName, + String callingAttributionTag); NetworkCapabilities getNetworkCapabilities(in Network network, String callingPackageName, String callingAttributionTag); + NetworkCapabilities redactNetworkCapabilitiesForPackage(in NetworkCapabilities nc, int uid, + String callingPackageName, String callingAttributionTag); + @UnsupportedAppUsage(maxTargetSdk = 30, trackingBug = 170729553) NetworkState[] getAllNetworkState(); diff --git a/service/src/com/android/server/ConnectivityService.java b/service/src/com/android/server/ConnectivityService.java index 2b7093414e..6944ca9883 100644 --- a/service/src/com/android/server/ConnectivityService.java +++ b/service/src/com/android/server/ConnectivityService.java @@ -2081,6 +2081,19 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + @Override + @Nullable + public LinkProperties redactLinkPropertiesForPackage(@NonNull LinkProperties lp, int uid, + @NonNull String packageName, @Nullable String callingAttributionTag) { + Objects.requireNonNull(packageName); + Objects.requireNonNull(lp); + enforceNetworkStackOrSettingsPermission(); + if (!checkAccessPermission(-1 /* pid */, uid)) { + return null; + } + return linkPropertiesRestrictedForCallerPermissions(lp, -1 /* callerPid */, uid); + } + private NetworkCapabilities getNetworkCapabilitiesInternal(Network network) { return getNetworkCapabilitiesInternal(getNetworkAgentInfoForNetwork(network)); } @@ -2104,13 +2117,34 @@ public class ConnectivityService extends IConnectivityManager.Stub getCallingPid(), mDeps.getCallingUid(), callingPackageName, callingAttributionTag); } + @Override + public NetworkCapabilities redactNetworkCapabilitiesForPackage(@NonNull NetworkCapabilities nc, + int uid, @NonNull String packageName, @Nullable String callingAttributionTag) { + Objects.requireNonNull(nc); + Objects.requireNonNull(packageName); + enforceNetworkStackOrSettingsPermission(); + if (!checkAccessPermission(-1 /* pid */, uid)) { + return null; + } + return createWithLocationInfoSanitizedIfNecessaryWhenParceled( + networkCapabilitiesRestrictedForCallerPermissions(nc, -1 /* callerPid */, uid), + true /* includeLocationSensitiveInfo */, -1 /* callingPid */, uid, packageName, + callingAttributionTag); + } + @VisibleForTesting NetworkCapabilities networkCapabilitiesRestrictedForCallerPermissions( NetworkCapabilities nc, int callerPid, int callerUid) { + // NoteĀ : here it would be nice to check ACCESS_NETWORK_STATE and return null, but + // this would be expensive (one more permission check every time any NC callback is + // sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if + // it happens for some reason (e.g. the package is uninstalled while CS is trying to + // send the callback) it would crash the system server with NPE. final NetworkCapabilities newNc = new NetworkCapabilities(nc); if (!checkSettingsPermission(callerPid, callerUid)) { newNc.setUids(null); newNc.setSSID(null); + // TODO: Processes holding NETWORK_FACTORY should be able to see the underlying networks newNc.setUnderlyingNetworks(null); } if (newNc.getNetworkSpecifier() != null) { @@ -2128,7 +2162,7 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Wrapper used to cache the permission check results performed for the corresponding - * app. This avoid performing multiple permission checks for different fields in + * app. This avoids performing multiple permission checks for different fields in * NetworkCapabilities. * Note: This wrapper does not support any sort of invalidation and thus must not be * persistent or long-lived. It may only be used for the time necessary to @@ -2256,6 +2290,8 @@ public class ConnectivityService extends IConnectivityManager.Stub includeLocationSensitiveInfo); final NetworkCapabilities newNc = new NetworkCapabilities(nc, redactions); // Reset owner uid if not destined for the owner app. + // TODO : calling UID is redacted because apps should generally not know what UID is + // bringing up the VPN, but this should not apply to some very privileged apps like settings if (callingUid != nc.getOwnerUid()) { newNc.setOwnerUid(INVALID_UID); return newNc; @@ -2281,9 +2317,15 @@ public class ConnectivityService extends IConnectivityManager.Stub return newNc; } + @NonNull private LinkProperties linkPropertiesRestrictedForCallerPermissions( LinkProperties lp, int callerPid, int callerUid) { if (lp == null) return new LinkProperties(); + // NoteĀ : here it would be nice to check ACCESS_NETWORK_STATE and return null, but + // this would be expensive (one more permission check every time any LP callback is + // sent) and possibly dangerous : apps normally can't lose ACCESS_NETWORK_STATE, if + // it happens for some reason (e.g. the package is uninstalled while CS is trying to + // send the callback) it would crash the system server with NPE. // Only do a permission check if sanitization is needed, to avoid unnecessary binder calls. final boolean needsSanitization = @@ -2654,6 +2696,11 @@ public class ConnectivityService extends IConnectivityManager.Stub "ConnectivityService"); } + private boolean checkAccessPermission(int pid, int uid) { + return mContext.checkPermission(android.Manifest.permission.ACCESS_NETWORK_STATE, pid, uid) + == PERMISSION_GRANTED; + } + /** * Performs a strict and comprehensive check of whether a calling package is allowed to * change the state of network, as the condition differs for pre-M, M+, and diff --git a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java index 53e4ab757d..ea64252200 100644 --- a/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java +++ b/tests/cts/net/src/android/net/cts/ConnectivityManagerTest.java @@ -16,9 +16,15 @@ package android.net.cts; +import static android.Manifest.permission.ACCESS_COARSE_LOCATION; +import static android.Manifest.permission.ACCESS_FINE_LOCATION; +import static android.Manifest.permission.ACCESS_NETWORK_STATE; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS; +import static android.Manifest.permission.NETWORK_FACTORY; import static android.Manifest.permission.NETWORK_SETTINGS; +import static android.Manifest.permission.NETWORK_SETUP_WIZARD; +import static android.Manifest.permission.NETWORK_STACK; import static android.Manifest.permission.READ_DEVICE_CONFIG; import static android.content.pm.PackageManager.FEATURE_BLUETOOTH; import static android.content.pm.PackageManager.FEATURE_ETHERNET; @@ -54,7 +60,9 @@ import static android.net.NetworkCapabilities.NET_CAPABILITY_NOT_RESTRICTED; import static android.net.NetworkCapabilities.NET_CAPABILITY_PARTIAL_CONNECTIVITY; import static android.net.NetworkCapabilities.NET_CAPABILITY_VALIDATED; import static android.net.NetworkCapabilities.TRANSPORT_TEST; +import static android.net.NetworkCapabilities.TRANSPORT_VPN; import static android.net.NetworkCapabilities.TRANSPORT_WIFI; +import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.net.cts.util.CtsNetUtils.ConnectivityActionReceiver; import static android.net.cts.util.CtsNetUtils.HTTP_PORT; import static android.net.cts.util.CtsNetUtils.NETWORK_CALLBACK_ACTION; @@ -64,6 +72,7 @@ import static android.net.cts.util.CtsTetheringUtils.TestTetheringEventCallback; import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTPS_URL; import static android.net.util.NetworkStackUtils.TEST_CAPTIVE_PORTAL_HTTP_URL; import static android.os.MessageQueue.OnFileDescriptorEventListener.EVENT_INPUT; +import static android.os.Process.INVALID_UID; import static android.provider.Settings.Global.NETWORK_METERED_MULTIPATH_PREFERENCE; import static android.system.OsConstants.AF_INET; import static android.system.OsConstants.AF_INET6; @@ -76,6 +85,7 @@ import static com.android.modules.utils.build.SdkLevel.isAtLeastS; import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_LOCKDOWN_VPN; import static com.android.networkstack.apishim.ConstantsShim.BLOCKED_REASON_NONE; import static com.android.testutils.Cleanup.testAndCleanup; +import static com.android.testutils.DevSdkIgnoreRuleKt.SC_V2; import static com.android.testutils.MiscAsserts.assertThrows; import static com.android.testutils.TestNetworkTrackerKt.initTestNetwork; import static com.android.testutils.TestPermissionUtil.runAsShell; @@ -103,6 +113,7 @@ import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.res.Resources; +import android.net.CaptivePortalData; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; import android.net.ConnectivitySettingsManager; @@ -132,6 +143,7 @@ import android.net.Uri; import android.net.cts.util.CtsNetUtils; import android.net.cts.util.CtsTetheringUtils; import android.net.util.KeepaliveUtils; +import android.net.wifi.WifiInfo; import android.net.wifi.WifiManager; import android.os.Binder; import android.os.Build; @@ -160,6 +172,7 @@ import androidx.test.runner.AndroidJUnit4; import com.android.internal.util.ArrayUtils; import com.android.modules.utils.build.SdkLevel; +import com.android.net.module.util.CollectionUtils; import com.android.networkstack.apishim.ConnectivityManagerShimImpl; import com.android.networkstack.apishim.ConstantsShim; import com.android.networkstack.apishim.NetworkInformationShimImpl; @@ -554,6 +567,223 @@ public class ConnectivityManagerTest { } } + private boolean checkPermission(String perm, int uid) { + return mContext.checkPermission(perm, -1 /* pid */, uid) == PERMISSION_GRANTED; + } + + private String findPackageByPermissions(@NonNull List requiredPermissions, + @NonNull List forbiddenPermissions) throws Exception { + final List packageInfos = + mPackageManager.getInstalledPackages(GET_PERMISSIONS); + for (PackageInfo packageInfo : packageInfos) { + final int uid = mPackageManager.getPackageUid(packageInfo.packageName, 0 /* flags */); + if (!CollectionUtils.all(requiredPermissions, perm -> checkPermission(perm, uid))) { + continue; + } + if (CollectionUtils.any(forbiddenPermissions, perm -> checkPermission(perm, uid))) { + continue; + } + + return packageInfo.packageName; + } + return null; + } + + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) + @Test + public void testRedactLinkPropertiesForPackage() throws Exception { + final String groundedPkg = findPackageByPermissions( + List.of(), /* requiredPermissions */ + List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */); + assertNotNull("Couldn't find any package without ACCESS_NETWORK_STATE", groundedPkg); + final int groundedUid = mPackageManager.getPackageUid(groundedPkg, 0 /* flags */); + + final String normalPkg = findPackageByPermissions( + List.of(ACCESS_NETWORK_STATE) /* requiredPermissions */, + List.of(NETWORK_SETTINGS, NETWORK_STACK, + PERMISSION_MAINLINE_NETWORK_STACK) /* forbiddenPermissions */); + assertNotNull("Couldn't find any package with ACCESS_NETWORK_STATE but" + + " without NETWORK_SETTINGS", normalPkg); + final int normalUid = mPackageManager.getPackageUid(normalPkg, 0 /* flags */); + + // There are some privileged packages on the system, like the phone process, the network + // stack and the system server. + final String privilegedPkg = findPackageByPermissions( + List.of(ACCESS_NETWORK_STATE, NETWORK_SETTINGS), /* requiredPermissions */ + List.of() /* forbiddenPermissions */); + assertNotNull("Couldn't find a package with sufficient permissions", privilegedPkg); + final int privilegedUid = mPackageManager.getPackageUid(privilegedPkg, 0); + + // Set parcelSensitiveFields to true to preserve CaptivePortalApiUrl & CaptivePortalData + // when parceling. + final LinkProperties lp = new LinkProperties(new LinkProperties(), + true /* parcelSensitiveFields */); + final Uri capportUrl = Uri.parse("https://capport.example.com/api"); + final CaptivePortalData capportData = new CaptivePortalData.Builder().build(); + final int mtu = 12345; + lp.setMtu(mtu); + lp.setCaptivePortalApiUrl(capportUrl); + lp.setCaptivePortalData(capportData); + + // No matter what the given uid is, a SecurityException will be thrown if the caller + // doesn't hold the NETWORK_SETTINGS permission. + assertThrows(SecurityException.class, + () -> mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg)); + assertThrows(SecurityException.class, + () -> mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg)); + assertThrows(SecurityException.class, + () -> mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg)); + + runAsShell(NETWORK_SETTINGS, () -> { + // No matter what the given uid is, if the given LinkProperties is null, then + // NullPointerException will be thrown. + assertThrows(NullPointerException.class, + () -> mCm.redactLinkPropertiesForPackage(null, groundedUid, groundedPkg)); + assertThrows(NullPointerException.class, + () -> mCm.redactLinkPropertiesForPackage(null, normalUid, normalPkg)); + assertThrows(NullPointerException.class, + () -> mCm.redactLinkPropertiesForPackage(null, privilegedUid, privilegedPkg)); + + // Make sure null is returned for a UID without ACCESS_NETWORK_STATE. + assertNull(mCm.redactLinkPropertiesForPackage(lp, groundedUid, groundedPkg)); + + // CaptivePortalApiUrl & CaptivePortalData will be set to null if given uid doesn't hold + // the NETWORK_SETTINGS permission. + assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg) + .getCaptivePortalApiUrl()); + assertNull(mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg) + .getCaptivePortalData()); + // MTU is not sensitive and is not redacted. + assertEquals(mtu, mCm.redactLinkPropertiesForPackage(lp, normalUid, normalPkg) + .getMtu()); + + // CaptivePortalApiUrl & CaptivePortalData will be preserved if the given uid holds the + // NETWORK_SETTINGS permission. + assertEquals(capportUrl, + mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg) + .getCaptivePortalApiUrl()); + assertEquals(capportData, + mCm.redactLinkPropertiesForPackage(lp, privilegedUid, privilegedPkg) + .getCaptivePortalData()); + }); + } + + private NetworkCapabilities redactNc(@NonNull final NetworkCapabilities nc, int uid, + @NonNull String packageName) { + return mCm.redactNetworkCapabilitiesForPackage(nc, uid, packageName); + } + + @DevSdkIgnoreRule.IgnoreUpTo(SC_V2) + @Test + public void testRedactNetworkCapabilitiesForPackage() throws Exception { + final String groundedPkg = findPackageByPermissions( + List.of(), /* requiredPermissions */ + List.of(ACCESS_NETWORK_STATE) /* forbiddenPermissions */); + assertNotNull("Couldn't find any package without ACCESS_NETWORK_STATE", groundedPkg); + final int groundedUid = mPackageManager.getPackageUid(groundedPkg, 0 /* flags */); + + // A package which doesn't have any of the permissions below, but has NETWORK_STATE. + // There should be a number of packages like this on the device; AOSP has many, + // including contacts, webview, the keyboard, pacprocessor, messaging. + final String normalPkg = findPackageByPermissions( + List.of(ACCESS_NETWORK_STATE) /* requiredPermissions */, + List.of(NETWORK_SETTINGS, NETWORK_FACTORY, NETWORK_SETUP_WIZARD, + NETWORK_STACK, PERMISSION_MAINLINE_NETWORK_STACK, + ACCESS_FINE_LOCATION, ACCESS_COARSE_LOCATION) /* forbiddenPermissions */); + assertNotNull("Can't find a package with ACCESS_NETWORK_STATE but without any of" + + " the forbidden permissions", normalPkg); + final int normalUid = mPackageManager.getPackageUid(normalPkg, 0 /* flags */); + + // There are some privileged packages on the system, like the phone process, the network + // stack and the system server. + final String privilegedPkg = findPackageByPermissions( + List.of(ACCESS_NETWORK_STATE, NETWORK_SETTINGS, NETWORK_FACTORY, + ACCESS_FINE_LOCATION), /* requiredPermissions */ + List.of() /* forbiddenPermissions */); + assertNotNull("Couldn't find a package with sufficient permissions", privilegedPkg); + final int privilegedUid = mPackageManager.getPackageUid(privilegedPkg, 0); + + final Set> uids = new ArraySet<>(); + uids.add(new Range<>(10000, 10100)); + uids.add(new Range<>(10200, 10300)); + final String ssid = "My-WiFi"; + // This test will set underlying networks in the capabilities to redact to see if they + // are appropriately redacted, so fetch the default network to put in there as an example. + final Network defaultNetwork = mCm.getActiveNetwork(); + assertNotNull("CTS requires a working Internet connection", defaultNetwork); + final int subId1 = 1; + final int subId2 = 2; + final int[] administratorUids = {normalUid}; + final String bssid = "location sensitive"; + final int rssi = 43; // not location sensitive + final WifiInfo wifiInfo = new WifiInfo.Builder() + .setBssid(bssid) + .setRssi(rssi) + .build(); + final NetworkCapabilities nc = new NetworkCapabilities.Builder() + .setUids(uids) + .setSsid(ssid) + .setUnderlyingNetworks(List.of(defaultNetwork)) + .setSubscriptionIds(Set.of(subId1, subId2)) + .setAdministratorUids(administratorUids) + .setOwnerUid(normalUid) + .setTransportInfo(wifiInfo) + .build(); + + // No matter what the given uid is, a SecurityException will be thrown if the caller + // doesn't hold the NETWORK_SETTINGS permission. + assertThrows(SecurityException.class, () -> redactNc(nc, groundedUid, groundedPkg)); + assertThrows(SecurityException.class, () -> redactNc(nc, normalUid, normalPkg)); + assertThrows(SecurityException.class, () -> redactNc(nc, privilegedUid, privilegedPkg)); + + runAsShell(NETWORK_SETTINGS, () -> { + // 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 + // 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()); + assertEquals(WifiInfo.DEFAULT_MAC_ADDRESS, + ((WifiInfo) redactedNormal.getTransportInfo()).getBSSID()); + assertEquals(rssi, ((WifiInfo) redactedNormal.getTransportInfo()).getRssi()); + + // Uids, ssid, underlying networks & subscriptionIds will be preserved if the given uid + // holds the associated permissions. + final NetworkCapabilities redactedPrivileged = + redactNc(nc, privilegedUid, privilegedPkg); + assertEquals(uids, redactedPrivileged.getUids()); + assertEquals(ssid, redactedPrivileged.getSsid()); + assertEquals(List.of(defaultNetwork), redactedPrivileged.getUnderlyingNetworks()); + assertEquals(Set.of(subId1, subId2), redactedPrivileged.getSubscriptionIds()); + assertEquals(bssid, ((WifiInfo) redactedPrivileged.getTransportInfo()).getBSSID()); + assertEquals(rssi, ((WifiInfo) redactedPrivileged.getTransportInfo()).getRssi()); + + // The owner uid is only preserved when the network is a VPN and the uid is the + // same as the owner uid. + nc.addTransportType(TRANSPORT_VPN); + assertEquals(normalUid, redactNc(nc, normalUid, normalPkg).getOwnerUid()); + assertEquals(INVALID_UID, redactNc(nc, privilegedUid, privilegedPkg).getOwnerUid()); + nc.removeTransportType(TRANSPORT_VPN); + + // If the given uid doesn't hold location permissions, the owner uid will be set to + // INVALID_UID even when sent to that UID (this avoids a wifi suggestor knowing where + // the device is by virtue of the device connecting to its own network). + assertEquals(INVALID_UID, redactNc(nc, normalUid, normalPkg).getOwnerUid()); + + // If the given uid holds location permissions, the owner uid is preserved. This works + // because the shell holds ACCESS_FINE_LOCATION. + final int[] administratorUids2 = { privilegedUid }; + nc.setAdministratorUids(administratorUids2); + nc.setOwnerUid(privilegedUid); + assertEquals(privilegedUid, redactNc(nc, privilegedUid, privilegedPkg).getOwnerUid()); + }); + } + /** * Tests that connections can be opened on WiFi and cellphone networks, * and that they are made from different IP addresses.