diff --git a/service/src/com/android/server/connectivity/PermissionMonitor.java b/service/src/com/android/server/connectivity/PermissionMonitor.java index 314889171a..673c80417b 100644 --- a/service/src/com/android/server/connectivity/PermissionMonitor.java +++ b/service/src/com/android/server/connectivity/PermissionMonitor.java @@ -24,6 +24,7 @@ import static android.Manifest.permission.UPDATE_DEVICE_STATS; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.Process.INVALID_UID; import static android.os.Process.SYSTEM_UID; @@ -39,6 +40,7 @@ import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageManager.NameNotFoundException; +import android.database.ContentObserver; import android.net.ConnectivitySettingsManager; import android.net.INetd; import android.net.UidRange; @@ -49,6 +51,7 @@ import android.os.ServiceSpecificException; import android.os.SystemConfigManager; import android.os.UserHandle; import android.os.UserManager; +import android.provider.Settings; import android.system.OsConstants; import android.util.ArraySet; import android.util.Log; @@ -68,7 +71,6 @@ import java.util.Map; import java.util.Map.Entry; import java.util.Set; - /** * A utility class to inform Netd of UID permisisons. * Does a mass update at boot and then monitors for app install/remove. @@ -145,6 +147,22 @@ public class PermissionMonitor { public int getDeviceFirstSdkInt() { return Build.VERSION.FIRST_SDK_INT; } + + /** + * Get apps allowed to use restricted networks via ConnectivitySettingsManager. + */ + public Set getAppsAllowedOnRestrictedNetworks(@NonNull Context context) { + return ConnectivitySettingsManager.getAppsAllowedOnRestrictedNetworks(context); + } + + /** + * Register ContentObserver for given Uri. + */ + public void registerContentObserver(@NonNull Context context, @NonNull Uri uri, + boolean notifyForDescendants, @NonNull ContentObserver observer) { + context.getContentResolver().registerContentObserver( + uri, notifyForDescendants, observer); + } } public PermissionMonitor(@NonNull final Context context, @NonNull final INetd netd) { @@ -167,18 +185,30 @@ public class PermissionMonitor { public synchronized void startMonitoring() { log("Monitoring"); + final Context userAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */); final IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); - mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */).registerReceiver( + userAllContext.registerReceiver( mIntentReceiver, intentFilter, null /* broadcastPermission */, null /* scheduler */); + // Register APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting observer + mDeps.registerContentObserver( + userAllContext, + Settings.Secure.getUriFor(APPS_ALLOWED_ON_RESTRICTED_NETWORKS), + false /* notifyForDescendants */, + new ContentObserver(null) { + @Override + public void onChange(boolean selfChange) { + onSettingChanged(); + } + }); + // Read APPS_ALLOWED_ON_RESTRICTED_NETWORKS setting and update // mAppsAllowedOnRestrictedNetworks. - updateAppsAllowedOnRestrictedNetworks( - ConnectivitySettingsManager.getAppsAllowedOnRestrictedNetworks(mContext)); + updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext)); List apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS | MATCH_ANY_USER); @@ -435,6 +465,20 @@ public class PermissionMonitor { mAllApps.add(UserHandle.getAppId(uid)); } + private Boolean highestUidNetworkPermission(int uid) { + Boolean permission = null; + final String[] packages = mPackageManager.getPackagesForUid(uid); + if (!CollectionUtils.isEmpty(packages)) { + for (String name : packages) { + permission = highestPermissionForUid(permission, name); + if (permission == SYSTEM) { + break; + } + } + } + return permission; + } + /** * Called when a package is removed. * @@ -465,19 +509,14 @@ public class PermissionMonitor { } Map apps = new HashMap<>(); - Boolean permission = null; - String[] packages = mPackageManager.getPackagesForUid(uid); - if (packages != null && packages.length > 0) { - for (String name : packages) { - permission = highestPermissionForUid(permission, name); - if (permission == SYSTEM) { - // An app with this UID still has the SYSTEM permission. - // Therefore, this UID must already have the SYSTEM permission. - // Nothing to do. - return; - } - } + final Boolean permission = highestUidNetworkPermission(uid); + if (permission == SYSTEM) { + // An app with this UID still has the SYSTEM permission. + // Therefore, this UID must already have the SYSTEM permission. + // Nothing to do. + return; } + if (permission == mApps.get(uid)) { // The permissions of this UID have not changed. Nothing to do. return; @@ -730,6 +769,38 @@ public class PermissionMonitor { return mVpnUidRanges.get(iface); } + private synchronized void onSettingChanged() { + // Step1. Update apps allowed to use restricted networks and compute the set of packages to + // update. + final Set packagesToUpdate = new ArraySet<>(mAppsAllowedOnRestrictedNetworks); + updateAppsAllowedOnRestrictedNetworks(mDeps.getAppsAllowedOnRestrictedNetworks(mContext)); + packagesToUpdate.addAll(mAppsAllowedOnRestrictedNetworks); + + final Map updatedApps = new HashMap<>(); + final Map removedApps = new HashMap<>(); + + // Step2. For each package to update, find out its new permission. + for (String app : packagesToUpdate) { + final PackageInfo info = getPackageInfo(app); + if (info == null || info.applicationInfo == null) continue; + + final int uid = info.applicationInfo.uid; + final Boolean permission = highestUidNetworkPermission(uid); + + if (null == permission) { + removedApps.put(uid, NETWORK); // Doesn't matter which permission is set here. + mApps.remove(uid); + } else { + updatedApps.put(uid, permission); + mApps.put(uid, permission); + } + } + + // Step3. Update or revoke permission for uids with netd. + update(mUsers, updatedApps, true /* add */); + update(mUsers, removedApps, false /* add */); + } + /** Dump info to dumpsys */ public void dump(IndentingPrintWriter pw) { pw.println("Interface filtering rules:"); diff --git a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java index c797a7b0ed..c75618f43c 100644 --- a/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java +++ b/tests/unit/java/com/android/server/connectivity/PermissionMonitorTest.java @@ -30,6 +30,7 @@ import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_GRANTED; import static android.content.pm.PackageInfo.REQUESTED_PERMISSION_REQUIRED; import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; +import static android.net.ConnectivitySettingsManager.APPS_ALLOWED_ON_RESTRICTED_NETWORKS; import static android.net.NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK; import static android.os.Process.SYSTEM_UID; @@ -44,8 +45,10 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertTrue; import static org.mockito.AdditionalMatchers.aryEq; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.argThat; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; @@ -62,6 +65,7 @@ import android.content.Intent; import android.content.pm.ApplicationInfo; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; +import android.database.ContentObserver; import android.net.INetd; import android.net.UidRange; import android.net.Uri; @@ -69,8 +73,6 @@ import android.os.Build; import android.os.SystemConfigManager; import android.os.UserHandle; import android.os.UserManager; -import android.provider.Settings; -import android.test.mock.MockContentResolver; import android.util.ArraySet; import android.util.SparseIntArray; @@ -78,9 +80,6 @@ import androidx.test.InstrumentationRegistry; import androidx.test.filters.SmallTest; import androidx.test.runner.AndroidJUnit4; -import com.android.internal.util.test.FakeSettingsProvider; - -import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -127,7 +126,6 @@ public class PermissionMonitorTest { @Mock private SystemConfigManager mSystemConfigManager; private PermissionMonitor mPermissionMonitor; - private MockContentResolver mContentResolver; @Before public void setUp() throws Exception { @@ -144,11 +142,7 @@ public class PermissionMonitorTest { final Context asUserCtx = mock(Context.class, AdditionalAnswers.delegatesTo(mContext)); doReturn(UserHandle.ALL).when(asUserCtx).getUser(); when(mContext.createContextAsUser(eq(UserHandle.ALL), anyInt())).thenReturn(asUserCtx); - - FakeSettingsProvider.clearSettingsProvider(); - mContentResolver = new MockContentResolver(); - mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider()); - when(mContext.getContentResolver()).thenReturn(mContentResolver); + when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>()); mPermissionMonitor = spy(new PermissionMonitor(mContext, mNetdService, mDeps)); @@ -156,11 +150,6 @@ public class PermissionMonitorTest { mPermissionMonitor.startMonitoring(); } - @After - public void tearDown() throws Exception { - FakeSettingsProvider.clearSettingsProvider(); - } - private boolean hasRestrictedNetworkPermission(String partition, int targetSdkVersion, int uid, String... permissions) { return hasRestrictedNetworkPermission( @@ -911,4 +900,102 @@ public class PermissionMonitorTest { mNetdServiceMonitor.expectPermission(INetd.PERMISSION_UNINSTALLED, new int[] { MOCK_UID1 }); } -} + @Test + public void testAppsAllowedOnRestrictedNetworksChanged() throws Exception { + final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService); + final ArgumentCaptor captor = + ArgumentCaptor.forClass(ContentObserver.class); + verify(mDeps, times(1)).registerContentObserver(any(), + argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)), + anyBoolean(), captor.capture()); + final ContentObserver contentObserver = captor.getValue(); + + mPermissionMonitor.onUserAdded(MOCK_USER1); + // Prepare PackageInfo for MOCK_PACKAGE1 + final PackageInfo packageInfo = buildPackageInfo( + false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1); + packageInfo.packageName = MOCK_PACKAGE1; + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), anyInt())).thenReturn(packageInfo); + when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{MOCK_PACKAGE1}); + // Prepare PackageInfo for MOCK_PACKAGE2 + final PackageInfo packageInfo2 = buildPackageInfo( + false /* hasSystemPermission */, MOCK_UID2, MOCK_USER1); + packageInfo2.packageName = MOCK_PACKAGE2; + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2); + when(mPackageManager.getPackagesForUid(MOCK_UID2)).thenReturn(new String[]{MOCK_PACKAGE2}); + + // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1 + // should have SYSTEM permission. + when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn( + new ArraySet<>(new String[] { MOCK_PACKAGE1 })); + contentObserver.onChange(true /* selfChange */); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1}); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2}); + + // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID2 + // should have SYSTEM permission but MOCK_UID1 should revoke permission. + when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn( + new ArraySet<>(new String[] { MOCK_PACKAGE2 })); + contentObserver.onChange(true /* selfChange */); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID2}); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1}); + + // No app lists in setting, should revoke permission from all uids. + when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>()); + contentObserver.onChange(true /* selfChange */); + mNetdMonitor.expectNoPermission( + new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1, MOCK_UID2}); + } + + @Test + public void testAppsAllowedOnRestrictedNetworksChangedWithSharedUid() throws Exception { + final NetdMonitor mNetdMonitor = new NetdMonitor(mNetdService); + final ArgumentCaptor captor = + ArgumentCaptor.forClass(ContentObserver.class); + verify(mDeps, times(1)).registerContentObserver(any(), + argThat(uri -> uri.getEncodedPath().contains(APPS_ALLOWED_ON_RESTRICTED_NETWORKS)), + anyBoolean(), captor.capture()); + final ContentObserver contentObserver = captor.getValue(); + + mPermissionMonitor.onUserAdded(MOCK_USER1); + // Prepare PackageInfo for MOCK_PACKAGE1 and MOCK_PACKAGE2 with shared uid MOCK_UID1. + final PackageInfo packageInfo = systemPackageInfoWithPermissions(CHANGE_NETWORK_STATE); + packageInfo.applicationInfo.uid = MOCK_USER1.getUid(MOCK_UID1); + packageInfo.packageName = MOCK_PACKAGE1; + final PackageInfo packageInfo2 = buildPackageInfo( + false /* hasSystemPermission */, MOCK_UID1, MOCK_USER1); + packageInfo2.packageName = MOCK_PACKAGE2; + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE1), anyInt())).thenReturn(packageInfo); + when(mPackageManager.getPackageInfo(eq(MOCK_PACKAGE2), anyInt())).thenReturn(packageInfo2); + when(mPackageManager.getPackagesForUid(MOCK_UID1)) + .thenReturn(new String[]{MOCK_PACKAGE1, MOCK_PACKAGE2}); + + // MOCK_PACKAGE1 have CHANGE_NETWORK_STATE, MOCK_UID1 should have NETWORK permission. + addPackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1}); + + // MOCK_PACKAGE2 is listed in setting that allow to use restricted networks, MOCK_UID1 + // should upgrade to SYSTEM permission. + when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn( + new ArraySet<>(new String[] { MOCK_PACKAGE2 })); + contentObserver.onChange(true /* selfChange */); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1}); + + // MOCK_PACKAGE1 is listed in setting that allow to use restricted networks, MOCK_UID1 + // should still have SYSTEM permission. + when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn( + new ArraySet<>(new String[] { MOCK_PACKAGE1 })); + contentObserver.onChange(true /* selfChange */); + mNetdMonitor.expectPermission(SYSTEM, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1}); + + // No app lists in setting, MOCK_UID1 should downgrade to NETWORK permission. + when(mDeps.getAppsAllowedOnRestrictedNetworks(any())).thenReturn(new ArraySet<>()); + contentObserver.onChange(true /* selfChange */); + mNetdMonitor.expectPermission(NETWORK, new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1}); + + // MOCK_PACKAGE1 removed, should revoke permission from MOCK_UID1. + when(mPackageManager.getPackagesForUid(MOCK_UID1)).thenReturn(new String[]{MOCK_PACKAGE2}); + removePackageForUsers(new UserHandle[]{MOCK_USER1}, MOCK_PACKAGE1, MOCK_UID1); + mNetdMonitor.expectNoPermission(new UserHandle[]{MOCK_USER1}, new int[]{MOCK_UID1}); + } +} \ No newline at end of file