Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0b5a5f8c authored by Ying Xu's avatar Ying Xu
Browse files

Delete the VPN profile if the owner app is removed

Bug: 440083803
Flag: android.net.platform.flags.delete_vpn_profile_when_app_uninstalled
Test: atest VpnManagerServiceTest
      Tested with a VPN app on device
Change-Id: Ia7a722f3ae09e024db5f96fc09da8a4e9559c53b
parent 767b9c9a
Loading
Loading
Loading
Loading
+11 −0
Original line number Diff line number Diff line
@@ -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
  }
}
+6 −0
Original line number Diff line number Diff line
@@ -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;
@@ -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();
+50 −2
Original line number Diff line number Diff line
@@ -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.
         *
@@ -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. */
@@ -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;
@@ -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.
     *
@@ -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.
     *
+19 −0
Original line number Diff line number Diff line
@@ -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;

@@ -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;
@@ -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;
@@ -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;
@@ -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]);
    }
}
+20 −0
Original line number Diff line number Diff line
@@ -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 =