diff --git a/core/api/system-current.txt b/core/api/system-current.txt index db3cedd27a9be..4248f9111de37 100644 --- a/core/api/system-current.txt +++ b/core/api/system-current.txt @@ -475,6 +475,8 @@ package android.app { field public static final String OPSTR_AUTO_REVOKE_PERMISSIONS_IF_UNUSED = "android:auto_revoke_permissions_if_unused"; field public static final String OPSTR_BIND_ACCESSIBILITY_SERVICE = "android:bind_accessibility_service"; field public static final String OPSTR_CHANGE_WIFI_STATE = "android:change_wifi_state"; + field public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager"; + field public static final String OPSTR_ESTABLISH_VPN_SERVICE = "android:establish_vpn_service"; field public static final String OPSTR_GET_ACCOUNTS = "android:get_accounts"; field public static final String OPSTR_GPS = "android:gps"; field public static final String OPSTR_INSTANT_APP_START_FOREGROUND = "android:instant_app_start_foreground"; diff --git a/core/java/android/app/AppOpsManager.java b/core/java/android/app/AppOpsManager.java index 0b336f6fc47f3..63f700b8883c3 100644 --- a/core/java/android/app/AppOpsManager.java +++ b/core/java/android/app/AppOpsManager.java @@ -1313,9 +1313,23 @@ public class AppOpsManager { public static final int OP_RECORD_INCOMING_PHONE_AUDIO = AppProtoEnums.APP_OP_RECORD_INCOMING_PHONE_AUDIO; + /** + * VPN app establishes a connection through the VpnService API. + * + * @hide + */ + public static final int OP_ESTABLISH_VPN_SERVICE = AppProtoEnums.APP_OP_ESTABLISH_VPN_SERVICE; + + /** + * VPN app establishes a connection through the VpnManager API. + * + * @hide + */ + public static final int OP_ESTABLISH_VPN_MANAGER = AppProtoEnums.APP_OP_ESTABLISH_VPN_MANAGER; + /** @hide */ @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) - public static final int _NUM_OP = 117; + public static final int _NUM_OP = 119; /** Access to coarse location information. */ public static final String OPSTR_COARSE_LOCATION = "android:coarse_location"; @@ -1754,6 +1768,22 @@ public class AppOpsManager { public static final String OPSTR_RECORD_INCOMING_PHONE_AUDIO = "android:record_incoming_phone_audio"; + /** + * VPN app establishes a connection through the VpnService API. + * + * @hide + */ + @SystemApi + public static final String OPSTR_ESTABLISH_VPN_SERVICE = "android:establish_vpn_service"; + + /** + * VPN app establishes a connection through the VpnManager API. + * + * @hide + */ + @SystemApi + public static final String OPSTR_ESTABLISH_VPN_MANAGER = "android:establish_vpn_manager"; + /** {@link #sAppOpsToNote} not initialized yet for this op */ private static final byte SHOULD_COLLECT_NOTE_OP_NOT_INITIALIZED = 0; /** Should not collect noting of this app-op in {@link #sAppOpsToNote} */ @@ -1970,6 +2000,8 @@ public class AppOpsManager { OP_BLUETOOTH_ADVERTISE, // OP_BLUETOOTH_ADVERTISE OP_RECORD_INCOMING_PHONE_AUDIO, // OP_RECORD_INCOMING_PHONE_AUDIO OP_NEARBY_WIFI_DEVICES, // OP_NEARBY_WIFI_DEVICES + OP_ESTABLISH_VPN_SERVICE, // OP_ESTABLISH_VPN_SERVICE + OP_ESTABLISH_VPN_MANAGER, // OP_ESTABLISH_VPN_MANAGER }; /** @@ -2093,6 +2125,8 @@ public class AppOpsManager { OPSTR_BLUETOOTH_ADVERTISE, OPSTR_RECORD_INCOMING_PHONE_AUDIO, OPSTR_NEARBY_WIFI_DEVICES, + OPSTR_ESTABLISH_VPN_SERVICE, + OPSTR_ESTABLISH_VPN_MANAGER, }; /** @@ -2216,7 +2250,9 @@ public class AppOpsManager { "ACTIVITY_RECOGNITION_SOURCE", "BLUETOOTH_ADVERTISE", "RECORD_INCOMING_PHONE_AUDIO", - "NEARBY_WIFI_DEVICES" + "NEARBY_WIFI_DEVICES", + "ESTABLISH_VPN_SERVICE", + "ESTABLISH_VPN_MANAGER", }; /** @@ -2342,6 +2378,8 @@ public class AppOpsManager { Manifest.permission.BLUETOOTH_ADVERTISE, null, // no permission for OP_RECORD_INCOMING_PHONE_AUDIO, Manifest.permission.NEARBY_WIFI_DEVICES, + null, // no permission for OP_ESTABLISH_VPN_SERVICE + null, // no permission for OP_ESTABLISH_VPN_MANAGER }; /** @@ -2467,6 +2505,8 @@ public class AppOpsManager { null, // BLUETOOTH_ADVERTISE null, // RECORD_INCOMING_PHONE_AUDIO null, // NEARBY_WIFI_DEVICES + null, // ESTABLISH_VPN_SERVICE + null, // ESTABLISH_VPN_MANAGER }; /** @@ -2591,6 +2631,8 @@ public class AppOpsManager { null, // BLUETOOTH_ADVERTISE null, // RECORD_INCOMING_PHONE_AUDIO null, // NEARBY_WIFI_DEVICES + null, // ESTABLISH_VPN_SERVICE + null, // ESTABLISH_VPN_MANAGER }; /** @@ -2714,6 +2756,8 @@ public class AppOpsManager { AppOpsManager.MODE_ALLOWED, // BLUETOOTH_ADVERTISE AppOpsManager.MODE_ALLOWED, // RECORD_INCOMING_PHONE_AUDIO AppOpsManager.MODE_ALLOWED, // NEARBY_WIFI_DEVICES + AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_SERVICE + AppOpsManager.MODE_ALLOWED, // ESTABLISH_VPN_MANAGER }; /** @@ -2841,6 +2885,8 @@ public class AppOpsManager { false, // BLUETOOTH_ADVERTISE false, // RECORD_INCOMING_PHONE_AUDIO false, // NEARBY_WIFI_DEVICES + false, // OP_ESTABLISH_VPN_SERVICE + false, // OP_ESTABLISH_VPN_MANAGER }; /** diff --git a/services/core/java/com/android/server/connectivity/Vpn.java b/services/core/java/com/android/server/connectivity/Vpn.java index 243a336b31f25..bf4ef4879c9ab 100644 --- a/services/core/java/com/android/server/connectivity/Vpn.java +++ b/services/core/java/com/android/server/connectivity/Vpn.java @@ -126,6 +126,7 @@ import com.android.server.net.BaseNetworkObserver; import libcore.io.IoUtils; import java.io.File; +import java.io.FileDescriptor; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; @@ -195,6 +196,7 @@ public class Vpn { private final Context mContext; private final ConnectivityManager mConnectivityManager; + private final AppOpsManager mAppOpsManager; // The context is for specific user which is created from mUserId private final Context mUserIdContext; @VisibleForTesting final Dependencies mDeps; @@ -407,6 +409,46 @@ public class Vpn { public boolean isInterfacePresent(final Vpn vpn, final String iface) { return vpn.jniCheck(iface) != 0; } + + /** + * @see ParcelFileDescriptor#adoptFd(int) + */ + public ParcelFileDescriptor adoptFd(Vpn vpn, int mtu) { + return ParcelFileDescriptor.adoptFd(jniCreate(vpn, mtu)); + } + + /** + * Call native method to create the VPN interface and return the FileDescriptor of /dev/tun. + */ + public int jniCreate(Vpn vpn, int mtu) { + return vpn.jniCreate(mtu); + } + + /** + * Call native method to get the interface name of VPN. + */ + public String jniGetName(Vpn vpn, int fd) { + return vpn.jniGetName(fd); + } + + /** + * Call native method to set the VPN addresses and return the number of addresses. + */ + public int jniSetAddresses(Vpn vpn, String interfaze, String addresses) { + return vpn.jniSetAddresses(interfaze, addresses); + } + + /** + * @see IoUtils#setBlocking(FileDescriptor, boolean) + */ + public void setBlocking(FileDescriptor fd, boolean blocking) { + try { + IoUtils.setBlocking(fd, blocking); + } catch (IOException e) { + throw new IllegalStateException( + "Cannot set tunnel's fd as blocking=" + blocking, e); + } + } } public Vpn(Looper looper, Context context, INetworkManagementService netService, INetd netd, @@ -431,6 +473,7 @@ public class Vpn { mVpnProfileStore = vpnProfileStore; mContext = context; mConnectivityManager = mContext.getSystemService(ConnectivityManager.class); + mAppOpsManager = mContext.getSystemService(AppOpsManager.class); mUserIdContext = context.createContextAsUser(UserHandle.of(userId), 0 /* flags */); mDeps = deps; mNms = netService; @@ -826,7 +869,6 @@ public class Vpn { VpnProfile profile = getVpnProfilePrivileged(alwaysOnPackage); if (profile != null) { startVpnProfilePrivileged(profile, alwaysOnPackage); - // If the above startVpnProfilePrivileged() call returns, the Ikev2VpnProfile was // correctly parsed, and the VPN has started running in a different thread. The only // other possibility is that the above call threw an exception, which will be @@ -974,9 +1016,15 @@ public class Vpn { } catch (Exception e) { // ignore } + mAppOpsManager.finishOp( + AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE, mOwnerUID, mPackage, null); mContext.unbindService(mConnection); cleanupVpnStateLocked(); } else if (mVpnRunner != null) { + if (!VpnConfig.LEGACY_VPN.equals(mPackage)) { + mAppOpsManager.finishOp( + AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, mOwnerUID, mPackage, null); + } // cleanupVpnStateLocked() is called from mVpnRunner.exit() mVpnRunner.exit(); } @@ -1041,10 +1089,8 @@ public class Vpn { return false; } - final AppOpsManager appOpMgr = - (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); for (final String appOpStr : toChange) { - appOpMgr.setMode( + mAppOpsManager.setMode( appOpStr, uid, packageName, @@ -1366,9 +1412,9 @@ public class Vpn { Set> oldUsers = mNetworkCapabilities.getUids(); // Configure the interface. Abort if any of these steps fails. - ParcelFileDescriptor tun = ParcelFileDescriptor.adoptFd(jniCreate(config.mtu)); + final ParcelFileDescriptor tun = mDeps.adoptFd(this, config.mtu); try { - String interfaze = jniGetName(tun.getFd()); + final String interfaze = mDeps.jniGetName(this, tun.getFd()); // TEMP use the old jni calls until there is support for netd address setting StringBuilder builder = new StringBuilder(); @@ -1376,7 +1422,7 @@ public class Vpn { builder.append(" "); builder.append(address); } - if (jniSetAddresses(interfaze, builder.toString()) < 1) { + if (mDeps.jniSetAddresses(this, interfaze, builder.toString()) < 1) { throw new IllegalArgumentException("At least one address must be specified"); } Connection connection = new Connection(); @@ -1422,11 +1468,11 @@ public class Vpn { jniReset(oldInterface); } - try { - IoUtils.setBlocking(tun.getFileDescriptor(), config.blocking); - } catch (IOException e) { - throw new IllegalStateException( - "Cannot set tunnel's fd as blocking=" + config.blocking, e); + mDeps.setBlocking(tun.getFileDescriptor(), config.blocking); + // Record that the VPN connection is established by an app which uses VpnService API. + if (oldNetworkAgent != mNetworkAgent) { + mAppOpsManager.startOp( + AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE, mOwnerUID, mPackage, null, null); } } catch (RuntimeException e) { IoUtils.closeQuietly(tun); @@ -1781,9 +1827,17 @@ public class Vpn { synchronized (Vpn.this) { if (interfaze.equals(mInterface) && jniCheck(interfaze) == 0) { if (mConnection != null) { + mAppOpsManager.finishOp( + AppOpsManager.OPSTR_ESTABLISH_VPN_SERVICE, mOwnerUID, mPackage, + null); mContext.unbindService(mConnection); cleanupVpnStateLocked(); } else if (mVpnRunner != null) { + if (!VpnConfig.LEGACY_VPN.equals(mPackage)) { + mAppOpsManager.finishOp( + AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, mOwnerUID, mPackage, + null); + } // cleanupVpnStateLocked() is called from mVpnRunner.exit() mVpnRunner.exit(); } @@ -3254,8 +3308,7 @@ public class Vpn { * * @param packageName the package name of the app provisioning this profile */ - public synchronized void startVpnProfile( - @NonNull String packageName) { + public synchronized void startVpnProfile(@NonNull String packageName) { requireNonNull(packageName, "No package name provided"); enforceNotRestrictedUser(); @@ -3318,6 +3371,13 @@ public class Vpn { Log.d(TAG, "Unknown VPN profile type: " + profile.type); break; } + + // Record that the VPN connection is established by an app which uses VpnManager API. + if (!VpnConfig.LEGACY_VPN.equals(packageName)) { + mAppOpsManager.startOp( + AppOpsManager.OPSTR_ESTABLISH_VPN_MANAGER, mOwnerUID, mPackage, null, + null); + } } catch (GeneralSecurityException e) { // Reset mConfig mConfig = null;