All Downloads are FREE. Search and download functionalities are using the official Maven repository.

src.com.android.server.VpnManagerService Maven / Gradle / Ivy

/*
 * Copyright (C) 2021 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server;

import static android.Manifest.permission.NETWORK_STACK;

import static com.android.net.module.util.PermissionUtils.enforceAnyPermissionOf;

import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.net.ConnectivityManager;
import android.net.INetd;
import android.net.IVpnManager;
import android.net.LinkProperties;
import android.net.Network;
import android.net.NetworkStack;
import android.net.UnderlyingNetworkInfo;
import android.net.Uri;
import android.net.VpnManager;
import android.net.VpnService;
import android.net.util.NetdService;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.INetworkManagementService;
import android.os.ParcelFileDescriptor;
import android.os.Process;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.os.UserManager;
import android.security.Credentials;
import android.text.TextUtils;
import android.util.Log;
import android.util.SparseArray;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.net.LegacyVpnInfo;
import com.android.internal.net.VpnConfig;
import com.android.internal.net.VpnProfile;
import com.android.internal.util.DumpUtils;
import com.android.internal.util.IndentingPrintWriter;
import com.android.server.connectivity.Vpn;
import com.android.server.connectivity.VpnProfileStore;
import com.android.server.net.LockdownVpnTracker;

import java.io.FileDescriptor;
import java.io.PrintWriter;
import java.util.List;

/**
 * Service that tracks and manages VPNs, and backs the VpnService and VpnManager APIs.
 * @hide
 */
public class VpnManagerService extends IVpnManager.Stub {
    private static final String TAG = VpnManagerService.class.getSimpleName();

    @VisibleForTesting
    protected final HandlerThread mHandlerThread;
    private final Handler mHandler;

    private final Context mContext;
    private final Context mUserAllContext;

    private final Dependencies mDeps;

    private final ConnectivityManager mCm;
    private final VpnProfileStore mVpnProfileStore;
    private final INetworkManagementService mNMS;
    private final INetd mNetd;
    private final UserManager mUserManager;

    @VisibleForTesting
    @GuardedBy("mVpns")
    protected final SparseArray mVpns = new SparseArray<>();

    // TODO: investigate if mLockdownEnabled can be removed and replaced everywhere by
    // a direct call to LockdownVpnTracker.isEnabled().
    @GuardedBy("mVpns")
    private boolean mLockdownEnabled;
    @GuardedBy("mVpns")
    private LockdownVpnTracker mLockdownTracker;

    /**
     * Dependencies of VpnManager, for injection in tests.
     */
    @VisibleForTesting
    public static class Dependencies {
        /** Returns the calling UID of an IPC. */
        public int getCallingUid() {
            return Binder.getCallingUid();
        }

        /** Creates a HandlerThread to be used by this class. */
        public HandlerThread makeHandlerThread() {
            return new HandlerThread("VpnManagerService");
        }

        /** Return the VpnProfileStore to be used by this class */
        public VpnProfileStore getVpnProfileStore() {
            return new VpnProfileStore();
        }

        public INetd getNetd() {
            return NetdService.getInstance();
        }

        public INetworkManagementService getINetworkManagementService() {
            return INetworkManagementService.Stub.asInterface(
                    ServiceManager.getService(Context.NETWORKMANAGEMENT_SERVICE));
        }
    }

    public VpnManagerService(Context context, Dependencies deps) {
        mContext = context;
        mDeps = deps;
        mHandlerThread = mDeps.makeHandlerThread();
        mHandlerThread.start();
        mHandler = mHandlerThread.getThreadHandler();
        mVpnProfileStore = mDeps.getVpnProfileStore();
        mUserAllContext = mContext.createContextAsUser(UserHandle.ALL, 0 /* flags */);
        mCm = mContext.getSystemService(ConnectivityManager.class);
        mNMS = mDeps.getINetworkManagementService();
        mNetd = mDeps.getNetd();
        mUserManager = mContext.getSystemService(UserManager.class);
        registerReceivers();
        log("VpnManagerService starting up");
    }

    /** Creates a new VpnManagerService */
    public static VpnManagerService create(Context context) {
        return new VpnManagerService(context, new Dependencies());
    }

    /** Informs the service that the system is ready. */
    public void systemReady() {
        // Try bringing up tracker, but KeyStore won't be ready yet for secondary users so wait
        // for user to unlock device too.
        updateLockdownVpn();
    }

    @Override
    /** Dumps service state. */
    protected void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
            @Nullable String[] args) {
        if (!DumpUtils.checkDumpPermission(mContext, TAG, writer)) return;
        IndentingPrintWriter pw = new IndentingPrintWriter(writer, "  ");
        pw.println("VPNs:");
        pw.increaseIndent();
        synchronized (mVpns) {
            for (int i = 0; i < mVpns.size(); i++) {
                pw.println(mVpns.keyAt(i) + ": " + mVpns.valueAt(i).getPackage());
            }
            pw.decreaseIndent();
        }
    }

    /**
     * Prepare for a VPN application.
     * VPN permissions are checked in the {@link Vpn} class. If the caller is not {@code userId},
     * {@link android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
     *
     * @param oldPackage Package name of the application which currently controls VPN, which will
     *                   be replaced. If there is no such application, this should should either be
     *                   {@code null} or {@link VpnConfig.LEGACY_VPN}.
     * @param newPackage Package name of the application which should gain control of VPN, or
     *                   {@code null} to disable.
     * @param userId User for whom to prepare the new VPN.
     *
     * @hide
     */
    @Override
    public boolean prepareVpn(@Nullable String oldPackage, @Nullable String newPackage,
            int userId) {
        enforceCrossUserPermission(userId);

        synchronized (mVpns) {
            throwIfLockdownEnabled();
            Vpn vpn = mVpns.get(userId);
            if (vpn != null) {
                return vpn.prepare(oldPackage, newPackage, VpnManager.TYPE_VPN_SERVICE);
            } else {
                return false;
            }
        }
    }

    /**
     * Set whether the VPN package has the ability to launch VPNs without user intervention. This
     * method is used by system-privileged apps. VPN permissions are checked in the {@link Vpn}
     * class. If the caller is not {@code userId}, {@link
     * android.Manifest.permission.INTERACT_ACROSS_USERS_FULL} permission is required.
     *
     * @param packageName The package for which authorization state should change.
     * @param userId User for whom {@code packageName} is installed.
     * @param vpnType The {@link VpnManager.VpnType} constant representing what class of VPN
     *     permissions should be granted. When unauthorizing an app, {@link
     *     VpnManager.TYPE_VPN_NONE} should be used.
     * @hide
     */
    @Override
    public void setVpnPackageAuthorization(
            String packageName, int userId, @VpnManager.VpnType int vpnType) {
        enforceCrossUserPermission(userId);

        synchronized (mVpns) {
            Vpn vpn = mVpns.get(userId);
            if (vpn != null) {
                vpn.setPackageAuthorization(packageName, vpnType);
            }
        }
    }

    /**
     * Configure a TUN interface and return its file descriptor. Parameters
     * are encoded and opaque to this class. This method is used by VpnBuilder
     * and not available in VpnManager. Permissions are checked in
     * Vpn class.
     * @hide
     */
    @Override
    public ParcelFileDescriptor establishVpn(VpnConfig config) {
        int user = UserHandle.getUserId(mDeps.getCallingUid());
        synchronized (mVpns) {
            throwIfLockdownEnabled();
            return mVpns.get(user).establish(config);
        }
    }

    @Override
    public boolean addVpnAddress(String address, int prefixLength) {
        int user = UserHandle.getUserId(mDeps.getCallingUid());
        synchronized (mVpns) {
            throwIfLockdownEnabled();
            return mVpns.get(user).addAddress(address, prefixLength);
        }
    }

    @Override
    public boolean removeVpnAddress(String address, int prefixLength) {
        int user = UserHandle.getUserId(mDeps.getCallingUid());
        synchronized (mVpns) {
            throwIfLockdownEnabled();
            return mVpns.get(user).removeAddress(address, prefixLength);
        }
    }

    @Override
    public boolean setUnderlyingNetworksForVpn(Network[] networks) {
        int user = UserHandle.getUserId(mDeps.getCallingUid());
        final boolean success;
        synchronized (mVpns) {
            success = mVpns.get(user).setUnderlyingNetworks(networks);
        }
        return success;
    }

    /**
     * Stores the given VPN profile based on the provisioning package name.
     *
     * 

If there is already a VPN profile stored for the provisioning package, this call will * overwrite the profile. * *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed * exclusively by the Settings app, and passed into the platform at startup time. * * @return {@code true} if user consent has already been granted, {@code false} otherwise. * @hide */ @Override public boolean provisionVpnProfile(@NonNull VpnProfile profile, @NonNull String packageName) { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { return mVpns.get(user).provisionVpnProfile(packageName, profile); } } /** * Deletes the stored VPN profile for the provisioning package * *

If there are no profiles for the given package, this method will silently succeed. * *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed * exclusively by the Settings app, and passed into the platform at startup time. * * @hide */ @Override public void deleteVpnProfile(@NonNull String packageName) { final int user = UserHandle.getUserId(mDeps.getCallingUid()); synchronized (mVpns) { mVpns.get(user).deleteVpnProfile(packageName); } } // TODO : Move to a static lib to factorize with Vpn.java private int getAppUid(final String app, final int userId) { final PackageManager pm = mContext.getPackageManager(); final long token = Binder.clearCallingIdentity(); try { return pm.getPackageUidAsUser(app, userId); } catch (NameNotFoundException e) { return -1; } finally { Binder.restoreCallingIdentity(token); } } private void verifyCallingUidAndPackage(String packageName, int callingUid) { final int userId = UserHandle.getUserId(callingUid); if (getAppUid(packageName, userId) != callingUid) { throw new SecurityException(packageName + " does not belong to uid " + callingUid); } } /** * Starts the VPN based on the stored profile for the given package * *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed * exclusively by the Settings app, and passed into the platform at startup time. * * @throws IllegalArgumentException if no profile was found for the given package name. * @hide */ @Override public void startVpnProfile(@NonNull String packageName) { final int callingUid = Binder.getCallingUid(); verifyCallingUidAndPackage(packageName, callingUid); final int user = UserHandle.getUserId(callingUid); synchronized (mVpns) { throwIfLockdownEnabled(); mVpns.get(user).startVpnProfile(packageName); } } /** * Stops the Platform VPN if the provided package is running one. * *

This is designed to serve the VpnManager only; settings-based VPN profiles are managed * exclusively by the Settings app, and passed into the platform at startup time. * * @hide */ @Override public void stopVpnProfile(@NonNull String packageName) { final int callingUid = Binder.getCallingUid(); verifyCallingUidAndPackage(packageName, callingUid); final int user = UserHandle.getUserId(callingUid); synchronized (mVpns) { mVpns.get(user).stopVpnProfile(packageName); } } /** * Start legacy VPN, controlling native daemons as needed. Creates a * secondary thread to perform connection work, returning quickly. * * Legacy VPN is deprecated starting from Android S. So this API shouldn't be called if the * initial SDK version of device is Android S+. Otherwise, UnsupportedOperationException will be * thrown. */ @SuppressWarnings("AndroidFrameworkCompatChange") // This is not an app-visible API. @Override public void startLegacyVpn(VpnProfile profile) { if (Build.VERSION.DEVICE_INITIAL_SDK_INT >= Build.VERSION_CODES.S && VpnProfile.isLegacyType(profile.type)) { throw new UnsupportedOperationException("Legacy VPN is deprecated"); } int user = UserHandle.getUserId(mDeps.getCallingUid()); // Note that if the caller is not system (uid >= Process.FIRST_APPLICATION_UID), // the code might not work well since getActiveNetwork might return null if the uid is // blocked by NetworkPolicyManagerService. final LinkProperties egress = mCm.getLinkProperties(mCm.getActiveNetwork()); if (egress == null) { throw new IllegalStateException("Missing active network connection"); } synchronized (mVpns) { throwIfLockdownEnabled(); mVpns.get(user).startLegacyVpn(profile, null /* underlying */, egress); } } /** * Return the information of the ongoing legacy VPN. This method is used * by VpnSettings and not available in ConnectivityManager. Permissions * are checked in Vpn class. */ @Override public LegacyVpnInfo getLegacyVpnInfo(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { return mVpns.get(userId).getLegacyVpnInfo(); } } /** * Returns the information of the ongoing VPN for {@code userId}. This method is used by * VpnDialogs and not available in ConnectivityManager. * Permissions are checked in Vpn class. * @hide */ @Override public VpnConfig getVpnConfig(int userId) { enforceCrossUserPermission(userId); synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn != null) { return vpn.getVpnConfig(); } else { return null; } } } private boolean isLockdownVpnEnabled() { return mVpnProfileStore.get(Credentials.LOCKDOWN_VPN) != null; } @Override public boolean updateLockdownVpn() { // Allow the system UID for the system server and for Settings. // Also, for unit tests, allow the process that ConnectivityService is running in. if (mDeps.getCallingUid() != Process.SYSTEM_UID && Binder.getCallingPid() != Process.myPid()) { logw("Lockdown VPN only available to system process or AID_SYSTEM"); return false; } synchronized (mVpns) { // Tear down existing lockdown if profile was removed mLockdownEnabled = isLockdownVpnEnabled(); if (!mLockdownEnabled) { setLockdownTracker(null); return true; } byte[] profileTag = mVpnProfileStore.get(Credentials.LOCKDOWN_VPN); if (profileTag == null) { loge("Lockdown VPN configured but cannot be read from keystore"); return false; } String profileName = new String(profileTag); final VpnProfile profile = VpnProfile.decode( profileName, mVpnProfileStore.get(Credentials.VPN + profileName)); if (profile == null) { loge("Lockdown VPN configured invalid profile " + profileName); setLockdownTracker(null); return true; } int user = UserHandle.getUserId(mDeps.getCallingUid()); Vpn vpn = mVpns.get(user); if (vpn == null) { logw("VPN for user " + user + " not ready yet. Skipping lockdown"); return false; } setLockdownTracker( new LockdownVpnTracker(mContext, mHandler, vpn, profile)); } return true; } /** * Internally set new {@link LockdownVpnTracker}, shutting down any existing * {@link LockdownVpnTracker}. Can be {@code null} to disable lockdown. */ @GuardedBy("mVpns") private void setLockdownTracker(LockdownVpnTracker tracker) { // Shutdown any existing tracker final LockdownVpnTracker existing = mLockdownTracker; // TODO: Add a trigger when the always-on VPN enable/disable to reevaluate and send the // necessary onBlockedStatusChanged callbacks. mLockdownTracker = null; if (existing != null) { existing.shutdown(); } if (tracker != null) { mLockdownTracker = tracker; mLockdownTracker.init(); } } /** * Throws if there is any currently running, always-on Legacy VPN. * *

The LockdownVpnTracker and mLockdownEnabled both track whether an always-on Legacy VPN is * running across the entire system. Tracking for app-based VPNs is done on a per-user, * per-package basis in Vpn.java */ @GuardedBy("mVpns") private void throwIfLockdownEnabled() { if (mLockdownEnabled) { throw new IllegalStateException("Unavailable in lockdown mode"); } } /** * Starts the always-on VPN {@link VpnService} for user {@param userId}, which should perform * some setup and then call {@code establish()} to connect. * * @return {@code true} if the service was started, the service was already connected, or there * was no always-on VPN to start. {@code false} otherwise. */ private boolean startAlwaysOnVpn(int userId) { synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { // Shouldn't happen as all code paths that point here should have checked the Vpn // exists already. Log.wtf(TAG, "User " + userId + " has no Vpn configuration"); return false; } return vpn.startAlwaysOnVpn(); } } @Override public boolean isAlwaysOnVpnPackageSupported(int userId, String packageName) { enforceSettingsPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { logw("User " + userId + " has no Vpn configuration"); return false; } return vpn.isAlwaysOnPackageSupported(packageName); } } @Override public boolean setAlwaysOnVpnPackage( int userId, String packageName, boolean lockdown, List lockdownAllowlist) { enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { // Can't set always-on VPN if legacy VPN is already in lockdown mode. if (isLockdownVpnEnabled()) { return false; } Vpn vpn = mVpns.get(userId); if (vpn == null) { logw("User " + userId + " has no Vpn configuration"); return false; } if (!vpn.setAlwaysOnPackage(packageName, lockdown, lockdownAllowlist)) { return false; } if (!startAlwaysOnVpn(userId)) { vpn.setAlwaysOnPackage(null, false, null); return false; } } return true; } @Override public String getAlwaysOnVpnPackage(int userId) { enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { logw("User " + userId + " has no Vpn configuration"); return null; } return vpn.getAlwaysOnPackage(); } } @Override public boolean isVpnLockdownEnabled(int userId) { enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { logw("User " + userId + " has no Vpn configuration"); return false; } return vpn.getLockdown(); } } @Override public List getVpnLockdownAllowlist(int userId) { enforceControlAlwaysOnVpnPermission(); enforceCrossUserPermission(userId); synchronized (mVpns) { Vpn vpn = mVpns.get(userId); if (vpn == null) { logw("User " + userId + " has no Vpn configuration"); return null; } return vpn.getLockdownAllowlist(); } } @GuardedBy("mVpns") private Vpn getVpnIfOwner() { return getVpnIfOwner(mDeps.getCallingUid()); } // TODO: stop calling into Vpn.java and get this information from data in this class. @GuardedBy("mVpns") private Vpn getVpnIfOwner(int uid) { final int user = UserHandle.getUserId(uid); final Vpn vpn = mVpns.get(user); if (vpn == null) { return null; } else { final UnderlyingNetworkInfo info = vpn.getUnderlyingNetworkInfo(); return (info == null || info.getOwnerUid() != uid) ? null : vpn; } } private void registerReceivers() { // Set up the listener for user state for creating user VPNs. // Should run on mHandler to avoid any races. IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_STARTED); intentFilter.addAction(Intent.ACTION_USER_STOPPED); intentFilter.addAction(Intent.ACTION_USER_ADDED); intentFilter.addAction(Intent.ACTION_USER_REMOVED); intentFilter.addAction(Intent.ACTION_USER_UNLOCKED); mUserAllContext.registerReceiver( mIntentReceiver, intentFilter, null /* broadcastPermission */, mHandler); mContext.createContextAsUser(UserHandle.SYSTEM, 0 /* flags */).registerReceiver( mUserPresentReceiver, new IntentFilter(Intent.ACTION_USER_PRESENT), null /* broadcastPermission */, mHandler /* scheduler */); // Listen to package add and removal events for all users. intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_PACKAGE_REPLACED); intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); intentFilter.addDataScheme("package"); mUserAllContext.registerReceiver( mIntentReceiver, intentFilter, null /* broadcastPermission */, mHandler); // Listen to lockdown VPN reset. intentFilter = new IntentFilter(); intentFilter.addAction(LockdownVpnTracker.ACTION_LOCKDOWN_RESET); mUserAllContext.registerReceiver( mIntentReceiver, intentFilter, NETWORK_STACK, mHandler); } private BroadcastReceiver mIntentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { ensureRunningOnHandlerThread(); final String action = intent.getAction(); final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1); final Uri packageData = intent.getData(); final String packageName = packageData != null ? packageData.getSchemeSpecificPart() : null; if (LockdownVpnTracker.ACTION_LOCKDOWN_RESET.equals(action)) { onVpnLockdownReset(); } // UserId should be filled for below intents, check the existence. if (userId == UserHandle.USER_NULL) return; if (Intent.ACTION_USER_STARTED.equals(action)) { onUserStarted(userId); } else if (Intent.ACTION_USER_STOPPED.equals(action)) { onUserStopped(userId); } else if (Intent.ACTION_USER_ADDED.equals(action)) { onUserAdded(userId); } else if (Intent.ACTION_USER_REMOVED.equals(action)) { onUserRemoved(userId); } else if (Intent.ACTION_USER_UNLOCKED.equals(action)) { onUserUnlocked(userId); } else if (Intent.ACTION_PACKAGE_REPLACED.equals(action)) { onPackageReplaced(packageName, uid); } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { final boolean isReplacing = intent.getBooleanExtra( Intent.EXTRA_REPLACING, false); onPackageRemoved(packageName, uid, isReplacing); } else { Log.wtf(TAG, "received unexpected intent: " + action); } } }; private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { @Override public void onReceive(Context context, Intent intent) { ensureRunningOnHandlerThread(); // Try creating lockdown tracker, since user present usually means // unlocked keystore. updateLockdownVpn(); // Use the same context that registered receiver before to unregister it. Because use // different context to unregister receiver will cause exception. context.unregisterReceiver(this); } }; private void onUserStarted(int userId) { synchronized (mVpns) { Vpn userVpn = mVpns.get(userId); if (userVpn != null) { loge("Starting user already has a VPN"); return; } userVpn = new Vpn(mHandler.getLooper(), mContext, mNMS, mNetd, userId, new VpnProfileStore()); mVpns.put(userId, userVpn); if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { updateLockdownVpn(); } } } private void onUserStopped(int userId) { synchronized (mVpns) { Vpn userVpn = mVpns.get(userId); if (userVpn == null) { loge("Stopped user has no VPN"); return; } userVpn.onUserStopped(); mVpns.delete(userId); } } @Override public boolean isCallerCurrentAlwaysOnVpnApp() { synchronized (mVpns) { Vpn vpn = getVpnIfOwner(); return vpn != null && vpn.getAlwaysOn(); } } @Override public boolean isCallerCurrentAlwaysOnVpnLockdownApp() { synchronized (mVpns) { Vpn vpn = getVpnIfOwner(); return vpn != null && vpn.getLockdown(); } } private void onUserAdded(int userId) { synchronized (mVpns) { final int vpnsSize = mVpns.size(); for (int i = 0; i < vpnsSize; i++) { Vpn vpn = mVpns.valueAt(i); vpn.onUserAdded(userId); } } } private void onUserRemoved(int userId) { synchronized (mVpns) { final int vpnsSize = mVpns.size(); for (int i = 0; i < vpnsSize; i++) { Vpn vpn = mVpns.valueAt(i); vpn.onUserRemoved(userId); } } } private void onPackageReplaced(String packageName, int uid) { if (TextUtils.isEmpty(packageName) || uid < 0) { Log.wtf(TAG, "Invalid package in onPackageReplaced: " + packageName + " | " + uid); return; } final int userId = UserHandle.getUserId(uid); synchronized (mVpns) { final Vpn vpn = mVpns.get(userId); if (vpn == null) { return; } // Legacy always-on VPN won't be affected since the package name is not set. if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName)) { log("Restarting always-on VPN package " + packageName + " for user " + userId); vpn.startAlwaysOnVpn(); } } } private void onPackageRemoved(String packageName, int uid, boolean isReplacing) { if (TextUtils.isEmpty(packageName) || uid < 0) { Log.wtf(TAG, "Invalid package in onPackageRemoved: " + packageName + " | " + uid); return; } final int userId = UserHandle.getUserId(uid); synchronized (mVpns) { final Vpn vpn = mVpns.get(userId); if (vpn == null) { return; } // Legacy always-on VPN won't be affected since the package name is not set. if (TextUtils.equals(vpn.getAlwaysOnPackage(), packageName) && !isReplacing) { log("Removing always-on VPN package " + packageName + " for user " + userId); vpn.setAlwaysOnPackage(null, false, null); } } } private void onUserUnlocked(int userId) { synchronized (mVpns) { // User present may be sent because of an unlock, which might mean an unlocked keystore. if (mUserManager.getUserInfo(userId).isPrimary() && isLockdownVpnEnabled()) { updateLockdownVpn(); } else { startAlwaysOnVpn(userId); } } } private void onVpnLockdownReset() { synchronized (mVpns) { if (mLockdownTracker != null) mLockdownTracker.reset(); } } @Override public void factoryReset() { enforceSettingsPermission(); if (mUserManager.hasUserRestriction(UserManager.DISALLOW_NETWORK_RESET) || mUserManager.hasUserRestriction(UserManager.DISALLOW_CONFIG_VPN)) { return; } // Remove always-on package final int userId = UserHandle.getCallingUserId(); synchronized (mVpns) { final String alwaysOnPackage = getAlwaysOnVpnPackage(userId); if (alwaysOnPackage != null) { setAlwaysOnVpnPackage(userId, null, false, null); setVpnPackageAuthorization(alwaysOnPackage, userId, VpnManager.TYPE_VPN_NONE); } // Turn Always-on VPN off if (mLockdownEnabled && userId == UserHandle.USER_SYSTEM) { final long ident = Binder.clearCallingIdentity(); try { mVpnProfileStore.remove(Credentials.LOCKDOWN_VPN); mLockdownEnabled = false; setLockdownTracker(null); } finally { Binder.restoreCallingIdentity(ident); } } // Turn VPN off VpnConfig vpnConfig = getVpnConfig(userId); if (vpnConfig != null) { if (vpnConfig.legacy) { prepareVpn(VpnConfig.LEGACY_VPN, VpnConfig.LEGACY_VPN, userId); } else { // Prevent this app (packagename = vpnConfig.user) from initiating // VPN connections in the future without user intervention. setVpnPackageAuthorization( vpnConfig.user, userId, VpnManager.TYPE_VPN_NONE); prepareVpn(null, VpnConfig.LEGACY_VPN, userId); } } } } private void ensureRunningOnHandlerThread() { if (mHandler.getLooper().getThread() != Thread.currentThread()) { throw new IllegalStateException( "Not running on VpnManagerService thread: " + Thread.currentThread().getName()); } } private void enforceControlAlwaysOnVpnPermission() { mContext.enforceCallingOrSelfPermission( android.Manifest.permission.CONTROL_ALWAYS_ON_VPN, "VpnManagerService"); } /** * Require that the caller is either in the same user or has appropriate permission to interact * across users. * * @param userId Target user for whatever operation the current IPC is supposed to perform. */ private void enforceCrossUserPermission(int userId) { if (userId == UserHandle.getCallingUserId()) { // Not a cross-user call. return; } mContext.enforceCallingOrSelfPermission( android.Manifest.permission.INTERACT_ACROSS_USERS_FULL, "VpnManagerService"); } private void enforceSettingsPermission() { enforceAnyPermissionOf(mContext, android.Manifest.permission.NETWORK_SETTINGS, NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } private static void log(String s) { Log.d(TAG, s); } private static void logw(String s) { Log.w(TAG, s); } private static void loge(String s) { Log.e(TAG, s); } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy