Loading core/java/android/net/flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -77,3 +77,14 @@ flag { description: "Flag for adding CREATE_APP_SPECIFIC_NETWORK permission" bug: "435313135" } flag { name: "delete_vpn_profile_when_app_uninstalled" namespace: "android_core_networking" is_exported: false description: "Flag for deleting VPN profile when the provisioning app is uninstalled" bug: "440083803" metadata { purpose: PURPOSE_BUGFIX } } services/core/java/com/android/server/VpnManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server; import static android.Manifest.permission.NETWORK_STACK; import static android.net.platform.flags.Flags.deleteVpnProfileWhenAppUninstalled; import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf; import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission; Loading Loading @@ -889,6 +890,11 @@ public class VpnManagerService extends IVpnManager.Stub { log("Removing always-on VPN package " + packageName + " for user " + userId); vpn.setAlwaysOnPackage(null, false, null); } else if (deleteVpnProfileWhenAppUninstalled() && TextUtils.equals(vpn.getPackage(), packageName)) { log("Removing VPN package " + packageName + " for user " + userId); vpn.deleteVpnProfileDueToAppRemoval(packageName, uid); } vpn.refreshPlatformVpnAppExclusionList(); Loading services/core/java/com/android/server/connectivity/Vpn.java +50 −2 Original line number Diff line number Diff line Loading @@ -602,6 +602,16 @@ public class Vpn { } } /** Verify the binder calling UID is the one passed in arguments or the SYSTEM_UID */ public void verifyCallingUidOrSystemUidAndPackage( Context context, String packageName, int userId) { final int callingUid = Binder.getCallingUid(); if (getAppUid(context, packageName, userId) != callingUid && callingUid != Process.SYSTEM_UID) { throw new SecurityException(packageName + " does not belong to uid " + callingUid); } } /** * @see VpnConnectivityMetrics. * Loading Loading @@ -1308,8 +1318,12 @@ public class Vpn { // We can't just check that packageName matches mPackage, because if the app was uninstalled // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the // calling package may not be the same as the prepared package. Check both UID and package. return getAppUid(mContext, packageName, mUserId) == mOwnerUID && mPackage.equals(packageName); return isCurrentPreparedPackage(packageName, getAppUid(mContext, packageName, mUserId)); } @GuardedBy("this") private boolean isCurrentPreparedPackage(String packageName, int uid) { return uid == mOwnerUID && mPackage.equals(packageName); } /** Prepare the VPN for the given package. Does not perform permission checks. */ Loading Loading @@ -4091,6 +4105,10 @@ public class Vpn { mDeps.verifyCallingUidAndPackage(mContext, packageName, mUserId); } private void verifyCallingUidOrSystemUidAndPackage(String packageName) { mDeps.verifyCallingUidOrSystemUidAndPackage(mContext, packageName, mUserId); } @VisibleForTesting String getProfileNameForPackage(String packageName) { return Credentials.PLATFORM_VPN + mUserId + "_" + packageName; Loading Loading @@ -4159,6 +4177,10 @@ public class Vpn { return isCurrentPreparedPackage(packageName) && isIkev2VpnRunner(); } private boolean isCurrentIkev2VpnLocked(@NonNull String packageName, int uid) { return isCurrentPreparedPackage(packageName, uid) && isIkev2VpnRunner(); } /** * Deletes an app-provisioned VPN profile. * Loading Loading @@ -4190,6 +4212,32 @@ public class Vpn { } } /** * Deletes an app-provisioned VPN profile because the provisioning app has been uninstalled. * * @param packageName the package name of the app provisioning this profile * @param uid the uid of the app provisioning this profile */ public synchronized void deleteVpnProfileDueToAppRemoval( @NonNull String packageName, int uid) { requireNonNull(packageName, "No package name provided"); verifyCallingUidOrSystemUidAndPackage(packageName); enforceNotRestrictedUser(); final long token = Binder.clearCallingIdentity(); try { // If this profile is providing the current VPN, turn it off. if (isCurrentIkev2VpnLocked(packageName, uid)) { prepareInternal(VpnConfig.LEGACY_VPN); } getVpnProfileStore().remove(getProfileNameForPackage(packageName)); } finally { Binder.restoreCallingIdentity(token); } } /** * Retrieves the VpnProfile. * Loading services/tests/VpnTests/java/com/android/server/VpnManagerServiceTest.java +19 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server; import static android.net.platform.flags.Flags.FLAG_DELETE_VPN_PROFILE_WHEN_APP_UNINSTALLED; import static com.android.testutils.ContextUtils.mockService; import static com.android.testutils.MiscAsserts.assertThrows; Loading Loading @@ -45,6 +47,8 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.security.Credentials; import androidx.test.ext.junit.runners.AndroidJUnit4; Loading @@ -57,6 +61,7 @@ import com.android.server.net.LockdownVpnTracker; import com.android.testutils.HandlerUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading @@ -69,6 +74,9 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class VpnManagerServiceTest extends VpnTestBase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String CONTEXT_ATTRIBUTION_TAG = "VPN_MANAGER"; private static final int TIMEOUT_MS = 2_000; Loading Loading @@ -428,4 +436,15 @@ public class VpnManagerServiceTest extends VpnTestBase { mService.listFromVpnProfileStore(name); verify(mVpnProfileStore).list(name); } @Test @EnableFlags(FLAG_DELETE_VPN_PROFILE_WHEN_APP_UNINSTALLED) public void testRemoveVpnProfileOnPackageRemoved() { mService.startVpnProfile(TEST_VPN_PKG); doReturn(null).when(mVpn).getAlwaysOnPackage(); doReturn(PKGS[0]).when(mVpn).getPackage(); onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */); verify(mVpn).deleteVpnProfileDueToAppRemoval(PKGS[0], PKG_UIDS[0]); } } services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java +20 −0 Original line number Diff line number Diff line Loading @@ -1324,6 +1324,26 @@ public class VpnTest extends VpnTestBase { .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); } @Test public void testDeleteVpnProfileDueToAppRemoval() throws Exception { final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); vpn.startVpnProfile(TEST_VPN_PKG); verifyPlatformVpnIsActivated(TEST_VPN_PKG); // This mock is to make sure verifyCallingUidOrSystemUidAndPackage() would pass, the test // will fail since the unit test UID is not the SYSTEM_UID. when(mPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(Process.myUid()); vpn.deleteVpnProfileDueToAppRemoval( TEST_VPN_PKG, UserHandle.getUid(PRIMARY_USER.id, Process.myUid())); verify(mVpnProfileStore) .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); verifyPlatformVpnIsDeactivated(TEST_VPN_PKG); } @Test public void testDeleteVpnProfileRestrictedUser() throws Exception { final Vpn vpn = Loading Loading
core/java/android/net/flags.aconfig +11 −0 Original line number Diff line number Diff line Loading @@ -77,3 +77,14 @@ flag { description: "Flag for adding CREATE_APP_SPECIFIC_NETWORK permission" bug: "435313135" } flag { name: "delete_vpn_profile_when_app_uninstalled" namespace: "android_core_networking" is_exported: false description: "Flag for deleting VPN profile when the provisioning app is uninstalled" bug: "440083803" metadata { purpose: PURPOSE_BUGFIX } }
services/core/java/com/android/server/VpnManagerService.java +6 −0 Original line number Diff line number Diff line Loading @@ -17,6 +17,7 @@ package com.android.server; import static android.Manifest.permission.NETWORK_STACK; import static android.net.platform.flags.Flags.deleteVpnProfileWhenAppUninstalled; import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf; import static com.android.net.module.util.PermissionUtils.enforceNetworkStackPermission; Loading Loading @@ -889,6 +890,11 @@ public class VpnManagerService extends IVpnManager.Stub { log("Removing always-on VPN package " + packageName + " for user " + userId); vpn.setAlwaysOnPackage(null, false, null); } else if (deleteVpnProfileWhenAppUninstalled() && TextUtils.equals(vpn.getPackage(), packageName)) { log("Removing VPN package " + packageName + " for user " + userId); vpn.deleteVpnProfileDueToAppRemoval(packageName, uid); } vpn.refreshPlatformVpnAppExclusionList(); Loading
services/core/java/com/android/server/connectivity/Vpn.java +50 −2 Original line number Diff line number Diff line Loading @@ -602,6 +602,16 @@ public class Vpn { } } /** Verify the binder calling UID is the one passed in arguments or the SYSTEM_UID */ public void verifyCallingUidOrSystemUidAndPackage( Context context, String packageName, int userId) { final int callingUid = Binder.getCallingUid(); if (getAppUid(context, packageName, userId) != callingUid && callingUid != Process.SYSTEM_UID) { throw new SecurityException(packageName + " does not belong to uid " + callingUid); } } /** * @see VpnConnectivityMetrics. * Loading Loading @@ -1308,8 +1318,12 @@ public class Vpn { // We can't just check that packageName matches mPackage, because if the app was uninstalled // and reinstalled it will no longer be prepared. Similarly if there is a shared UID, the // calling package may not be the same as the prepared package. Check both UID and package. return getAppUid(mContext, packageName, mUserId) == mOwnerUID && mPackage.equals(packageName); return isCurrentPreparedPackage(packageName, getAppUid(mContext, packageName, mUserId)); } @GuardedBy("this") private boolean isCurrentPreparedPackage(String packageName, int uid) { return uid == mOwnerUID && mPackage.equals(packageName); } /** Prepare the VPN for the given package. Does not perform permission checks. */ Loading Loading @@ -4091,6 +4105,10 @@ public class Vpn { mDeps.verifyCallingUidAndPackage(mContext, packageName, mUserId); } private void verifyCallingUidOrSystemUidAndPackage(String packageName) { mDeps.verifyCallingUidOrSystemUidAndPackage(mContext, packageName, mUserId); } @VisibleForTesting String getProfileNameForPackage(String packageName) { return Credentials.PLATFORM_VPN + mUserId + "_" + packageName; Loading Loading @@ -4159,6 +4177,10 @@ public class Vpn { return isCurrentPreparedPackage(packageName) && isIkev2VpnRunner(); } private boolean isCurrentIkev2VpnLocked(@NonNull String packageName, int uid) { return isCurrentPreparedPackage(packageName, uid) && isIkev2VpnRunner(); } /** * Deletes an app-provisioned VPN profile. * Loading Loading @@ -4190,6 +4212,32 @@ public class Vpn { } } /** * Deletes an app-provisioned VPN profile because the provisioning app has been uninstalled. * * @param packageName the package name of the app provisioning this profile * @param uid the uid of the app provisioning this profile */ public synchronized void deleteVpnProfileDueToAppRemoval( @NonNull String packageName, int uid) { requireNonNull(packageName, "No package name provided"); verifyCallingUidOrSystemUidAndPackage(packageName); enforceNotRestrictedUser(); final long token = Binder.clearCallingIdentity(); try { // If this profile is providing the current VPN, turn it off. if (isCurrentIkev2VpnLocked(packageName, uid)) { prepareInternal(VpnConfig.LEGACY_VPN); } getVpnProfileStore().remove(getProfileNameForPackage(packageName)); } finally { Binder.restoreCallingIdentity(token); } } /** * Retrieves the VpnProfile. * Loading
services/tests/VpnTests/java/com/android/server/VpnManagerServiceTest.java +19 −0 Original line number Diff line number Diff line Loading @@ -16,6 +16,8 @@ package com.android.server; import static android.net.platform.flags.Flags.FLAG_DELETE_VPN_PROFILE_WHEN_APP_UNINSTALLED; import static com.android.testutils.ContextUtils.mockService; import static com.android.testutils.MiscAsserts.assertThrows; Loading Loading @@ -45,6 +47,8 @@ import android.os.INetworkManagementService; import android.os.Looper; import android.os.UserHandle; import android.os.UserManager; import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.security.Credentials; import androidx.test.ext.junit.runners.AndroidJUnit4; Loading @@ -57,6 +61,7 @@ import com.android.server.net.LockdownVpnTracker; import com.android.testutils.HandlerUtils; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.ArgumentCaptor; Loading @@ -69,6 +74,9 @@ import java.util.List; @RunWith(AndroidJUnit4.class) @SmallTest public class VpnManagerServiceTest extends VpnTestBase { @Rule public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); private static final String CONTEXT_ATTRIBUTION_TAG = "VPN_MANAGER"; private static final int TIMEOUT_MS = 2_000; Loading Loading @@ -428,4 +436,15 @@ public class VpnManagerServiceTest extends VpnTestBase { mService.listFromVpnProfileStore(name); verify(mVpnProfileStore).list(name); } @Test @EnableFlags(FLAG_DELETE_VPN_PROFILE_WHEN_APP_UNINSTALLED) public void testRemoveVpnProfileOnPackageRemoved() { mService.startVpnProfile(TEST_VPN_PKG); doReturn(null).when(mVpn).getAlwaysOnPackage(); doReturn(PKGS[0]).when(mVpn).getPackage(); onPackageRemoved(PKGS[0], PKG_UIDS[0], false /* isReplacing */); verify(mVpn).deleteVpnProfileDueToAppRemoval(PKGS[0], PKG_UIDS[0]); } }
services/tests/VpnTests/java/com/android/server/connectivity/VpnTest.java +20 −0 Original line number Diff line number Diff line Loading @@ -1324,6 +1324,26 @@ public class VpnTest extends VpnTestBase { .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); } @Test public void testDeleteVpnProfileDueToAppRemoval() throws Exception { final Vpn vpn = createVpn(AppOpsManager.OPSTR_ACTIVATE_PLATFORM_VPN); when(mVpnProfileStore.get(vpn.getProfileNameForPackage(TEST_VPN_PKG))) .thenReturn(mVpnProfile.encode()); vpn.startVpnProfile(TEST_VPN_PKG); verifyPlatformVpnIsActivated(TEST_VPN_PKG); // This mock is to make sure verifyCallingUidOrSystemUidAndPackage() would pass, the test // will fail since the unit test UID is not the SYSTEM_UID. when(mPackageManager.getPackageUidAsUser(any(), anyInt())).thenReturn(Process.myUid()); vpn.deleteVpnProfileDueToAppRemoval( TEST_VPN_PKG, UserHandle.getUid(PRIMARY_USER.id, Process.myUid())); verify(mVpnProfileStore) .remove(eq(vpn.getProfileNameForPackage(TEST_VPN_PKG))); verifyPlatformVpnIsDeactivated(TEST_VPN_PKG); } @Test public void testDeleteVpnProfileRestrictedUser() throws Exception { final Vpn vpn = Loading