Check location permission for ConnDiags last.

This CL updates ConnectivityService to check location permissions for
ConnectivityDiagnostics callbacks last in the permission check process.
This minimizes misattribution of location access for networks that an
app is not administering.

This CL also updates ConnectivityDiagnosticsManager documentation to
clearly state that location permissions are required in order to receive
callbacks.

Bug: 187310575
Test: atest ConnectivityDiagnosticsManagerTest
Test: atest ConnectivityServiceTest
Change-Id: I2dbeddac6273e2392ccaeae51a1c7776d6d3da75
This commit is contained in:
Cody Kesting
2021-05-05 13:17:22 -07:00
parent ca98d3d814
commit 160ef3936a
3 changed files with 46 additions and 27 deletions

View File

@@ -713,7 +713,9 @@ public class ConnectivityDiagnosticsManager {
* <p>Callbacks registered by apps not meeting the above criteria will not be invoked. * <p>Callbacks registered by apps not meeting the above criteria will not be invoked.
* *
* <p>If a registering app loses its relevant permissions, any callbacks it registered will * <p>If a registering app loses its relevant permissions, any callbacks it registered will
* silently stop receiving callbacks. * silently stop receiving callbacks. Note that registering apps must also have location
* permissions to receive callbacks as some Networks may be location-bound (such as WiFi
* networks).
* *
* <p>Each register() call <b>MUST</b> use a ConnectivityDiagnosticsCallback instance that is * <p>Each register() call <b>MUST</b> use a ConnectivityDiagnosticsCallback instance that is
* not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with * not currently registered. If a ConnectivityDiagnosticsCallback instance is registered with

View File

@@ -9175,6 +9175,34 @@ public class ConnectivityService extends IConnectivityManager.Stub
return results; return results;
} }
private boolean hasLocationPermission(String packageName, int uid) {
// LocationPermissionChecker#checkLocationPermission can throw SecurityException if the uid
// and package name don't match. Throwing on the CS thread is not acceptable, so wrap the
// call in a try-catch.
try {
if (!mLocationPermissionChecker.checkLocationPermission(
packageName, null /* featureId */, uid, null /* message */)) {
return false;
}
} catch (SecurityException e) {
return false;
}
return true;
}
private boolean ownsVpnRunningOverNetwork(int uid, Network network) {
for (NetworkAgentInfo virtual : mNetworkAgentInfos) {
if (virtual.supportsUnderlyingNetworks()
&& virtual.networkCapabilities.getOwnerUid() == uid
&& CollectionUtils.contains(virtual.declaredUnderlyingNetworks, network)) {
return true;
}
}
return false;
}
@VisibleForTesting @VisibleForTesting
boolean checkConnectivityDiagnosticsPermissions( boolean checkConnectivityDiagnosticsPermissions(
int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) { int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) {
@@ -9182,29 +9210,14 @@ public class ConnectivityService extends IConnectivityManager.Stub
return true; return true;
} }
// LocationPermissionChecker#checkLocationPermission can throw SecurityException if the uid // Administrator UIDs also contains the Owner UID
// and package name don't match. Throwing on the CS thread is not acceptable, so wrap the final int[] administratorUids = nai.networkCapabilities.getAdministratorUids();
// call in a try-catch. if (!CollectionUtils.contains(administratorUids, callbackUid)
try { && !ownsVpnRunningOverNetwork(callbackUid, nai.network)) {
if (!mLocationPermissionChecker.checkLocationPermission(
callbackPackageName, null /* featureId */, callbackUid, null /* message */)) {
return false;
}
} catch (SecurityException e) {
return false; return false;
} }
for (NetworkAgentInfo virtual : mNetworkAgentInfos) { return hasLocationPermission(callbackPackageName, callbackUid);
if (virtual.supportsUnderlyingNetworks()
&& virtual.networkCapabilities.getOwnerUid() == callbackUid
&& CollectionUtils.contains(virtual.declaredUnderlyingNetworks, nai.network)) {
return true;
}
}
// Administrator UIDs also contains the Owner UID
final int[] administratorUids = nai.networkCapabilities.getAdministratorUids();
return CollectionUtils.contains(administratorUids, callbackUid);
} }
@Override @Override

View File

@@ -9743,28 +9743,32 @@ public class ConnectivityServiceTest {
@Test @Test
public void testCheckConnectivityDiagnosticsPermissionsWrongUidPackageName() throws Exception { public void testCheckConnectivityDiagnosticsPermissionsWrongUidPackageName() throws Exception {
final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); final int wrongUid = Process.myUid() + 1;
final NetworkCapabilities nc = new NetworkCapabilities();
nc.setAdministratorUids(new int[] {wrongUid});
final NetworkAgentInfo naiWithUid = fakeMobileNai(nc);
mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
assertFalse( assertFalse(
"Mismatched uid/package name should not pass the location permission check", "Mismatched uid/package name should not pass the location permission check",
mService.checkConnectivityDiagnosticsPermissions( mService.checkConnectivityDiagnosticsPermissions(
Process.myPid() + 1, Process.myUid() + 1, naiWithoutUid, Process.myPid() + 1, wrongUid, naiWithUid, mContext.getOpPackageName()));
mContext.getOpPackageName()));
} }
@Test @Test
public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception { public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception {
final NetworkAgentInfo naiWithoutUid = fakeMobileNai(new NetworkCapabilities()); final NetworkCapabilities nc = new NetworkCapabilities();
nc.setAdministratorUids(new int[] {Process.myUid()});
final NetworkAgentInfo naiWithUid = fakeMobileNai(nc);
mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED);
assertFalse( assertFalse(
"ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics", "ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics",
mService.checkConnectivityDiagnosticsPermissions( mService.checkConnectivityDiagnosticsPermissions(
Process.myPid(), Process.myUid(), naiWithoutUid, Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName()));
mContext.getOpPackageName()));
} }
@Test @Test