diff options
6 files changed, 455 insertions, 102 deletions
diff --git a/core/java/android/net/ConnectivityDiagnosticsManager.java b/core/java/android/net/ConnectivityDiagnosticsManager.java index 140363c48227..b128ea7f3e39 100644 --- a/core/java/android/net/ConnectivityDiagnosticsManager.java +++ b/core/java/android/net/ConnectivityDiagnosticsManager.java @@ -676,7 +676,8 @@ public class ConnectivityDiagnosticsManager { } try { - mService.registerConnectivityDiagnosticsCallback(binder, request); + mService.registerConnectivityDiagnosticsCallback( + binder, request, mContext.getOpPackageName()); } catch (RemoteException exception) { exception.rethrowFromSystemServer(); } diff --git a/core/java/android/net/IConnectivityManager.aidl b/core/java/android/net/IConnectivityManager.aidl index 1089a197ff59..0fae607ca6ca 100644 --- a/core/java/android/net/IConnectivityManager.aidl +++ b/core/java/android/net/IConnectivityManager.aidl @@ -222,7 +222,7 @@ interface IConnectivityManager boolean isCallerCurrentAlwaysOnVpnLockdownApp(); void registerConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback, - in NetworkRequest request); + in NetworkRequest request, String callingPackageName); void unregisterConnectivityDiagnosticsCallback(in IConnectivityDiagnosticsCallback callback); IBinder startOrGetTestNetworkService(); diff --git a/core/java/android/net/NetworkCapabilities.java b/core/java/android/net/NetworkCapabilities.java index 4f4e27b446ef..cf5f2259af44 100644 --- a/core/java/android/net/NetworkCapabilities.java +++ b/core/java/android/net/NetworkCapabilities.java @@ -858,8 +858,8 @@ public final class NetworkCapabilities implements Parcelable { * * <p>In general, user-supplied networks (such as WiFi networks) do not have an administrator. * - * <p>An app is granted owner privileges over Networks that it supplies. Owner privileges - * implicitly include administrator privileges. + * <p>An app is granted owner privileges over Networks that it supplies. The owner UID MUST + * always be included in administratorUids. * * @param administratorUids the UIDs to be set as administrators of this Network. * @hide diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 2968a816835d..9702c50279d9 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -48,8 +48,11 @@ import static android.os.Process.INVALID_UID; import static android.system.OsConstants.IPPROTO_TCP; import static android.system.OsConstants.IPPROTO_UDP; +import static java.util.Map.Entry; + import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.AppOpsManager; import android.app.BroadcastOptions; import android.app.NotificationManager; import android.app.PendingIntent; @@ -62,6 +65,7 @@ import android.content.res.Configuration; import android.database.ContentObserver; import android.net.CaptivePortal; import android.net.ConnectionInfo; +import android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import android.net.ConnectivityManager; import android.net.ICaptivePortal; import android.net.IConnectivityDiagnosticsCallback; @@ -130,6 +134,7 @@ import android.os.Message; import android.os.Messenger; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.PersistableBundle; import android.os.PowerManager; import android.os.Process; import android.os.RemoteException; @@ -170,6 +175,7 @@ import com.android.internal.util.ArrayUtils; import com.android.internal.util.AsyncChannel; import com.android.internal.util.DumpUtils; import com.android.internal.util.IndentingPrintWriter; +import com.android.internal.util.LocationPermissionChecker; import com.android.internal.util.MessageUtils; import com.android.internal.util.XmlUtils; import com.android.server.am.BatteryStatsService; @@ -492,9 +498,9 @@ public class ConnectivityService extends IConnectivityManager.Stub /** * Event for NetworkMonitor/NetworkAgentInfo to inform ConnectivityService that the network has * been tested. - * obj = String representing URL that Internet probe was redirect to, if it was redirected. - * arg1 = One of the NETWORK_TESTED_RESULT_* constants. - * arg2 = NetID. + * obj = {@link NetworkTestedResults} representing information sent from NetworkMonitor. + * data = PersistableBundle of extras passed from NetworkMonitor. If {@link + * NetworkMonitorCallbacks#notifyNetworkTested} is called, this will be null. */ private static final int EVENT_NETWORK_TESTED = 41; @@ -596,6 +602,9 @@ public class ConnectivityService extends IConnectivityManager.Stub private Set<String> mWolSupportedInterfaces; private TelephonyManager mTelephonyManager; + private final AppOpsManager mAppOpsManager; + + private final LocationPermissionChecker mLocationPermissionChecker; private KeepaliveTracker mKeepaliveTracker; private NetworkNotificationManager mNotifier; @@ -992,6 +1001,8 @@ public class ConnectivityService extends IConnectivityManager.Stub mNetd = netd; mKeyStore = KeyStore.getInstance(); mTelephonyManager = (TelephonyManager) mContext.getSystemService(Context.TELEPHONY_SERVICE); + mAppOpsManager = (AppOpsManager) mContext.getSystemService(Context.APP_OPS_SERVICE); + mLocationPermissionChecker = new LocationPermissionChecker(mContext); // To ensure uid rules are synchronized with Network Policy, register for // NetworkPolicyManagerService events must happen prior to NetworkPolicyManagerService @@ -2101,6 +2112,12 @@ public class ConnectivityService extends IConnectivityManager.Stub NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); } + private boolean checkNetworkStackPermission(int pid, int uid) { + return checkAnyPermissionOf(pid, uid, + android.Manifest.permission.NETWORK_STACK, + NetworkStack.PERMISSION_MAINLINE_NETWORK_STACK); + } + private boolean checkNetworkSignalStrengthWakeupPermission(int pid, int uid) { return checkAnyPermissionOf(pid, uid, android.Manifest.permission.NETWORK_SIGNAL_STRENGTH_WAKEUP, @@ -2747,88 +2764,21 @@ public class ConnectivityService extends IConnectivityManager.Stub break; } case EVENT_NETWORK_TESTED: { - final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(msg.arg2); - if (nai == null) break; + final NetworkTestedResults results = (NetworkTestedResults) msg.obj; - final boolean wasPartial = nai.partialConnectivity; - nai.partialConnectivity = ((msg.arg1 & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); - final boolean partialConnectivityChanged = - (wasPartial != nai.partialConnectivity); - - final boolean valid = ((msg.arg1 & NETWORK_VALIDATION_RESULT_VALID) != 0); - final boolean wasValidated = nai.lastValidated; - final boolean wasDefault = isDefaultNetwork(nai); - // Only show a connected notification if the network is pending validation - // after the captive portal app was open, and it has now validated. - if (nai.captivePortalValidationPending && valid) { - // User is now logged in, network validated. - nai.captivePortalValidationPending = false; - showNetworkNotification(nai, NotificationType.LOGGED_IN); - } + final NetworkAgentInfo nai = getNetworkAgentInfoForNetId(results.mNetId); + if (nai == null) break; - final String redirectUrl = (msg.obj instanceof String) ? (String) msg.obj : ""; + handleNetworkTested(nai, results.mTestResult, + (results.mRedirectUrl == null) ? "" : results.mRedirectUrl); - if (DBG) { - final String logMsg = !TextUtils.isEmpty(redirectUrl) - ? " with redirect to " + redirectUrl - : ""; - log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg); - } - if (valid != nai.lastValidated) { - if (wasDefault) { - mDeps.getMetricsLogger() - .defaultNetworkMetrics().logDefaultNetworkValidity( - SystemClock.elapsedRealtime(), valid); - } - final int oldScore = nai.getCurrentScore(); - nai.lastValidated = valid; - nai.everValidated |= valid; - updateCapabilities(oldScore, nai, nai.networkCapabilities); - // If score has changed, rebroadcast to NetworkProviders. b/17726566 - if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); - if (valid) { - handleFreshlyValidatedNetwork(nai); - // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and - // LOST_INTERNET notifications if network becomes valid. - mNotifier.clearNotification(nai.network.netId, - NotificationType.NO_INTERNET); - mNotifier.clearNotification(nai.network.netId, - NotificationType.LOST_INTERNET); - mNotifier.clearNotification(nai.network.netId, - NotificationType.PARTIAL_CONNECTIVITY); - mNotifier.clearNotification(nai.network.netId, - NotificationType.PRIVATE_DNS_BROKEN); - // If network becomes valid, the hasShownBroken should be reset for - // that network so that the notification will be fired when the private - // DNS is broken again. - nai.networkAgentConfig.hasShownBroken = false; - } - } else if (partialConnectivityChanged) { - updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); - } - updateInetCondition(nai); - // Let the NetworkAgent know the state of its network - Bundle redirectUrlBundle = new Bundle(); - redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl); - // TODO: Evaluate to update partial connectivity to status to NetworkAgent. - nai.asyncChannel.sendMessage( - NetworkAgent.CMD_REPORT_NETWORK_STATUS, - (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), - 0, redirectUrlBundle); - - // If NetworkMonitor detects partial connectivity before - // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification - // immediately. Re-notify partial connectivity silently if no internet - // notification already there. - if (!wasPartial && nai.partialConnectivity) { - // Remove delayed message if there is a pending message. - mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network); - handlePromptUnvalidated(nai.network); - } - - if (wasValidated && !nai.lastValidated) { - handleNetworkUnvalidated(nai); - } + // Invoke ConnectivityReport generation for this Network test event. + final Message m = + mConnectivityDiagnosticsHandler.obtainMessage( + ConnectivityDiagnosticsHandler.EVENT_NETWORK_TESTED, + new ConnectivityReportEvent(results.mTimestampMillis, nai)); + m.setData(msg.getData()); + mConnectivityDiagnosticsHandler.sendMessage(m); break; } case EVENT_PROVISIONING_NOTIFICATION: { @@ -2879,6 +2829,87 @@ public class ConnectivityService extends IConnectivityManager.Stub return true; } + private void handleNetworkTested( + @NonNull NetworkAgentInfo nai, int testResult, @NonNull String redirectUrl) { + final boolean wasPartial = nai.partialConnectivity; + nai.partialConnectivity = ((testResult & NETWORK_VALIDATION_RESULT_PARTIAL) != 0); + final boolean partialConnectivityChanged = + (wasPartial != nai.partialConnectivity); + + final boolean valid = ((testResult & NETWORK_VALIDATION_RESULT_VALID) != 0); + final boolean wasValidated = nai.lastValidated; + final boolean wasDefault = isDefaultNetwork(nai); + // Only show a connected notification if the network is pending validation + // after the captive portal app was open, and it has now validated. + if (nai.captivePortalValidationPending && valid) { + // User is now logged in, network validated. + nai.captivePortalValidationPending = false; + showNetworkNotification(nai, NotificationType.LOGGED_IN); + } + + if (DBG) { + final String logMsg = !TextUtils.isEmpty(redirectUrl) + ? " with redirect to " + redirectUrl + : ""; + log(nai.name() + " validation " + (valid ? "passed" : "failed") + logMsg); + } + if (valid != nai.lastValidated) { + if (wasDefault) { + mDeps.getMetricsLogger() + .defaultNetworkMetrics().logDefaultNetworkValidity( + SystemClock.elapsedRealtime(), valid); + } + final int oldScore = nai.getCurrentScore(); + nai.lastValidated = valid; + nai.everValidated |= valid; + updateCapabilities(oldScore, nai, nai.networkCapabilities); + // If score has changed, rebroadcast to NetworkProviders. b/17726566 + if (oldScore != nai.getCurrentScore()) sendUpdatedScoreToFactories(nai); + if (valid) { + handleFreshlyValidatedNetwork(nai); + // Clear NO_INTERNET, PRIVATE_DNS_BROKEN, PARTIAL_CONNECTIVITY and + // LOST_INTERNET notifications if network becomes valid. + mNotifier.clearNotification(nai.network.netId, + NotificationType.NO_INTERNET); + mNotifier.clearNotification(nai.network.netId, + NotificationType.LOST_INTERNET); + mNotifier.clearNotification(nai.network.netId, + NotificationType.PARTIAL_CONNECTIVITY); + mNotifier.clearNotification(nai.network.netId, + NotificationType.PRIVATE_DNS_BROKEN); + // If network becomes valid, the hasShownBroken should be reset for + // that network so that the notification will be fired when the private + // DNS is broken again. + nai.networkAgentConfig.hasShownBroken = false; + } + } else if (partialConnectivityChanged) { + updateCapabilities(nai.getCurrentScore(), nai, nai.networkCapabilities); + } + updateInetCondition(nai); + // Let the NetworkAgent know the state of its network + Bundle redirectUrlBundle = new Bundle(); + redirectUrlBundle.putString(NetworkAgent.REDIRECT_URL_KEY, redirectUrl); + // TODO: Evaluate to update partial connectivity to status to NetworkAgent. + nai.asyncChannel.sendMessage( + NetworkAgent.CMD_REPORT_NETWORK_STATUS, + (valid ? NetworkAgent.VALID_NETWORK : NetworkAgent.INVALID_NETWORK), + 0, redirectUrlBundle); + + // If NetworkMonitor detects partial connectivity before + // EVENT_PROMPT_UNVALIDATED arrives, show the partial connectivity notification + // immediately. Re-notify partial connectivity silently if no internet + // notification already there. + if (!wasPartial && nai.partialConnectivity) { + // Remove delayed message if there is a pending message. + mHandler.removeMessages(EVENT_PROMPT_UNVALIDATED, nai.network); + handlePromptUnvalidated(nai.network); + } + + if (wasValidated && !nai.lastValidated) { + handleNetworkUnvalidated(nai); + } + } + private int getCaptivePortalMode() { return Settings.Global.getInt(mContext.getContentResolver(), Settings.Global.CAPTIVE_PORTAL_MODE, @@ -2927,8 +2958,23 @@ public class ConnectivityService extends IConnectivityManager.Stub @Override public void notifyNetworkTested(int testResult, @Nullable String redirectUrl) { - mTrackerHandler.sendMessage(mTrackerHandler.obtainMessage(EVENT_NETWORK_TESTED, - testResult, mNetId, redirectUrl)); + notifyNetworkTestedWithExtras(testResult, redirectUrl, SystemClock.elapsedRealtime(), + PersistableBundle.EMPTY); + } + + @Override + public void notifyNetworkTestedWithExtras( + int testResult, + @Nullable String redirectUrl, + long timestampMillis, + @NonNull PersistableBundle extras) { + final Message msg = + mTrackerHandler.obtainMessage( + EVENT_NETWORK_TESTED, + new NetworkTestedResults( + mNetId, testResult, timestampMillis, redirectUrl)); + msg.setData(new Bundle(extras)); + mTrackerHandler.sendMessage(msg); } @Override @@ -7359,7 +7405,11 @@ public class ConnectivityService extends IConnectivityManager.Stub @GuardedBy("mVpns") private Vpn getVpnIfOwner() { - final int uid = Binder.getCallingUid(); + return getVpnIfOwner(Binder.getCallingUid()); + } + + @GuardedBy("mVpns") + private Vpn getVpnIfOwner(int uid) { final int user = UserHandle.getUserId(uid); final Vpn vpn = mVpns.get(user); @@ -7471,6 +7521,17 @@ public class ConnectivityService extends IConnectivityManager.Stub */ private static final int EVENT_UNREGISTER_CONNECTIVITY_DIAGNOSTICS_CALLBACK = 2; + /** + * Event for {@link NetworkStateTrackerHandler} to trigger ConnectivityReport callbacks + * after processing {@link #EVENT_NETWORK_TESTED} events. + * obj = {@link ConnectivityReportEvent} representing ConnectivityReport info reported from + * NetworkMonitor. + * data = PersistableBundle of extras passed from NetworkMonitor. + * + * <p>See {@link ConnectivityService#EVENT_NETWORK_TESTED}. + */ + private static final int EVENT_NETWORK_TESTED = ConnectivityService.EVENT_NETWORK_TESTED; + private ConnectivityDiagnosticsHandler(Looper looper) { super(looper); } @@ -7488,6 +7549,19 @@ public class ConnectivityService extends IConnectivityManager.Stub (IConnectivityDiagnosticsCallback) msg.obj, msg.arg1); break; } + case EVENT_NETWORK_TESTED: { + final ConnectivityReportEvent reportEvent = + (ConnectivityReportEvent) msg.obj; + + // This is safe because {@link + // NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} receives a + // PersistableBundle and converts it to the Bundle in the incoming Message. If + // {@link NetworkMonitorCallbacks#notifyNetworkTested} is called, msg.data will + // not be set. This is also safe, as msg.getData() will return an empty Bundle. + final PersistableBundle extras = new PersistableBundle(msg.getData()); + handleNetworkTestedWithExtras(reportEvent, extras); + break; + } } } } @@ -7497,12 +7571,16 @@ public class ConnectivityService extends IConnectivityManager.Stub class ConnectivityDiagnosticsCallbackInfo implements Binder.DeathRecipient { @NonNull private final IConnectivityDiagnosticsCallback mCb; @NonNull private final NetworkRequestInfo mRequestInfo; + @NonNull private final String mCallingPackageName; @VisibleForTesting ConnectivityDiagnosticsCallbackInfo( - @NonNull IConnectivityDiagnosticsCallback cb, @NonNull NetworkRequestInfo nri) { + @NonNull IConnectivityDiagnosticsCallback cb, + @NonNull NetworkRequestInfo nri, + @NonNull String callingPackageName) { mCb = cb; mRequestInfo = nri; + mCallingPackageName = callingPackageName; } @Override @@ -7512,6 +7590,39 @@ public class ConnectivityService extends IConnectivityManager.Stub } } + /** + * Class used for sending information from {@link + * NetworkMonitorCallbacks#notifyNetworkTestedWithExtras} to the handler for processing it. + */ + private static class NetworkTestedResults { + private final int mNetId; + private final int mTestResult; + private final long mTimestampMillis; + @Nullable private final String mRedirectUrl; + + private NetworkTestedResults( + int netId, int testResult, long timestampMillis, @Nullable String redirectUrl) { + mNetId = netId; + mTestResult = testResult; + mTimestampMillis = timestampMillis; + mRedirectUrl = redirectUrl; + } + } + + /** + * Class used for sending information from {@link NetworkStateTrackerHandler} to {@link + * ConnectivityDiagnosticsHandler}. + */ + private static class ConnectivityReportEvent { + private final long mTimestampMillis; + @NonNull private final NetworkAgentInfo mNai; + + private ConnectivityReportEvent(long timestampMillis, @NonNull NetworkAgentInfo nai) { + mTimestampMillis = timestampMillis; + mNai = nai; + } + } + private void handleRegisterConnectivityDiagnosticsCallback( @NonNull ConnectivityDiagnosticsCallbackInfo cbInfo) { ensureRunningOnConnectivityServiceThread(); @@ -7559,13 +7670,80 @@ public class ConnectivityService extends IConnectivityManager.Stub cb.asBinder().unlinkToDeath(mConnectivityDiagnosticsCallbacks.remove(cb), 0); } + private void handleNetworkTestedWithExtras( + @NonNull ConnectivityReportEvent reportEvent, @NonNull PersistableBundle extras) { + final NetworkAgentInfo nai = reportEvent.mNai; + final ConnectivityReport report = + new ConnectivityReport( + reportEvent.mNai.network, + reportEvent.mTimestampMillis, + nai.linkProperties, + nai.networkCapabilities, + extras); + final List<IConnectivityDiagnosticsCallback> results = + getMatchingPermissionedCallbacks(nai); + for (final IConnectivityDiagnosticsCallback cb : results) { + try { + cb.onConnectivityReport(report); + } catch (RemoteException ex) { + loge("Error invoking onConnectivityReport", ex); + } + } + } + + private List<IConnectivityDiagnosticsCallback> getMatchingPermissionedCallbacks( + @NonNull NetworkAgentInfo nai) { + final List<IConnectivityDiagnosticsCallback> results = new ArrayList<>(); + for (Entry<IConnectivityDiagnosticsCallback, ConnectivityDiagnosticsCallbackInfo> entry : + mConnectivityDiagnosticsCallbacks.entrySet()) { + final ConnectivityDiagnosticsCallbackInfo cbInfo = entry.getValue(); + final NetworkRequestInfo nri = cbInfo.mRequestInfo; + if (nai.satisfies(nri.request)) { + if (checkConnectivityDiagnosticsPermissions( + nri.mPid, nri.mUid, nai, cbInfo.mCallingPackageName)) { + results.add(entry.getKey()); + } + } + } + return results; + } + + @VisibleForTesting + boolean checkConnectivityDiagnosticsPermissions( + int callbackPid, int callbackUid, NetworkAgentInfo nai, String callbackPackageName) { + if (checkNetworkStackPermission(callbackPid, callbackUid)) { + return true; + } + + if (!mLocationPermissionChecker.checkLocationPermission( + callbackPackageName, null /* featureId */, callbackUid, null /* message */)) { + return false; + } + + synchronized (mVpns) { + if (getVpnIfOwner(callbackUid) != null) { + return true; + } + } + + // Administrator UIDs also contains the Owner UID + if (nai.networkCapabilities.getAdministratorUids().contains(callbackUid)) { + return true; + } + + return false; + } + @Override public void registerConnectivityDiagnosticsCallback( - @NonNull IConnectivityDiagnosticsCallback callback, @NonNull NetworkRequest request) { + @NonNull IConnectivityDiagnosticsCallback callback, + @NonNull NetworkRequest request, + @NonNull String callingPackageName) { if (request.legacyType != TYPE_NONE) { throw new IllegalArgumentException("ConnectivityManager.TYPE_* are deprecated." + " Please use NetworkCapabilities instead."); } + mAppOpsManager.checkPackage(Binder.getCallingUid(), callingPackageName); // This NetworkCapabilities is only used for matching to Networks. Clear out its owner uid // and administrator uids to be safe. @@ -7583,7 +7761,7 @@ public class ConnectivityService extends IConnectivityManager.Stub // callback's binder death. final NetworkRequestInfo nri = new NetworkRequestInfo(requestWithId); final ConnectivityDiagnosticsCallbackInfo cbInfo = - new ConnectivityDiagnosticsCallbackInfo(callback, nri); + new ConnectivityDiagnosticsCallbackInfo(callback, nri, callingPackageName); mConnectivityDiagnosticsHandler.sendMessage( mConnectivityDiagnosticsHandler.obtainMessage( diff --git a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java index 2d5df4f47e00..0628691c3345 100644 --- a/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java +++ b/tests/net/java/android/net/ConnectivityDiagnosticsManagerTest.java @@ -38,6 +38,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; import android.content.Context; import android.os.PersistableBundle; +import androidx.test.InstrumentationRegistry; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -58,21 +60,26 @@ public class ConnectivityDiagnosticsManagerTest { private static final Executor INLINE_EXECUTOR = x -> x.run(); - @Mock private Context mContext; @Mock private IConnectivityManager mService; @Mock private ConnectivityDiagnosticsCallback mCb; + private Context mContext; private ConnectivityDiagnosticsBinder mBinder; private ConnectivityDiagnosticsManager mManager; + private String mPackageName; + @Before public void setUp() { - mContext = mock(Context.class); + mContext = InstrumentationRegistry.getContext(); + mService = mock(IConnectivityManager.class); mCb = mock(ConnectivityDiagnosticsCallback.class); mBinder = new ConnectivityDiagnosticsBinder(mCb, INLINE_EXECUTOR); mManager = new ConnectivityDiagnosticsManager(mContext, mService); + + mPackageName = mContext.getOpPackageName(); } @After @@ -271,7 +278,7 @@ public class ConnectivityDiagnosticsManagerTest { mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); verify(mService).registerConnectivityDiagnosticsCallback( - any(ConnectivityDiagnosticsBinder.class), eq(request)); + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); } @@ -302,7 +309,7 @@ public class ConnectivityDiagnosticsManagerTest { // verify that re-registering is successful mManager.registerConnectivityDiagnosticsCallback(request, INLINE_EXECUTOR, mCb); verify(mService, times(2)).registerConnectivityDiagnosticsCallback( - any(ConnectivityDiagnosticsBinder.class), eq(request)); + any(ConnectivityDiagnosticsBinder.class), eq(request), eq(mPackageName)); assertTrue(ConnectivityDiagnosticsManager.sCallbacks.containsKey(mCb)); } diff --git a/tests/net/java/com/android/server/ConnectivityServiceTest.java b/tests/net/java/com/android/server/ConnectivityServiceTest.java index 028c1dad82e4..bd460272c824 100644 --- a/tests/net/java/com/android/server/ConnectivityServiceTest.java +++ b/tests/net/java/com/android/server/ConnectivityServiceTest.java @@ -23,6 +23,7 @@ import static android.content.pm.PackageManager.GET_PERMISSIONS; import static android.content.pm.PackageManager.MATCH_ANY_USER; import static android.content.pm.PackageManager.PERMISSION_DENIED; import static android.content.pm.PackageManager.PERMISSION_GRANTED; +import static android.net.ConnectivityDiagnosticsManager.ConnectivityReport; import static android.net.ConnectivityManager.ACTION_CAPTIVE_PORTAL_SIGN_IN; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION; import static android.net.ConnectivityManager.CONNECTIVITY_ACTION_SUPL; @@ -119,6 +120,7 @@ import static org.mockito.Mockito.when; import android.Manifest; import android.annotation.NonNull; import android.app.AlarmManager; +import android.app.AppOpsManager; import android.app.NotificationManager; import android.app.PendingIntent; import android.content.BroadcastReceiver; @@ -132,6 +134,7 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; +import android.location.LocationManager; import android.net.CaptivePortalData; import android.net.ConnectivityManager; import android.net.ConnectivityManager.NetworkCallback; @@ -177,6 +180,7 @@ import android.net.shared.PrivateDnsConfig; import android.net.util.MultinetworkPolicyTracker; import android.os.BadParcelableException; import android.os.Binder; +import android.os.Build; import android.os.Bundle; import android.os.ConditionVariable; import android.os.Handler; @@ -187,6 +191,7 @@ import android.os.Looper; import android.os.Parcel; import android.os.ParcelFileDescriptor; import android.os.Parcelable; +import android.os.PersistableBundle; import android.os.Process; import android.os.RemoteException; import android.os.SystemClock; @@ -218,6 +223,7 @@ import com.android.server.connectivity.DefaultNetworkMetrics; import com.android.server.connectivity.IpConnectivityMetrics; import com.android.server.connectivity.MockableSystemProperties; import com.android.server.connectivity.Nat464Xlat; +import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkNotificationManager.NotificationType; import com.android.server.connectivity.ProxyTracker; import com.android.server.connectivity.Vpn; @@ -292,6 +298,8 @@ public class ConnectivityServiceTest { private static final int UNREASONABLY_LONG_ALARM_WAIT_MS = 1000; + private static final long TIMESTAMP = 1234L; + private static final String CLAT_PREFIX = "v4-"; private static final String MOBILE_IFNAME = "test_rmnet_data0"; private static final String WIFI_IFNAME = "test_wlan0"; @@ -327,6 +335,8 @@ public class ConnectivityServiceTest { @Mock AlarmManager mAlarmManager; @Mock IConnectivityDiagnosticsCallback mConnectivityDiagnosticsCallback; @Mock IBinder mIBinder; + @Mock LocationManager mLocationManager; + @Mock AppOpsManager mAppOpsManager; private ArgumentCaptor<ResolverParamsParcel> mResolverParamsParcelCaptor = ArgumentCaptor.forClass(ResolverParamsParcel.class); @@ -412,6 +422,8 @@ public class ConnectivityServiceTest { if (Context.NETWORK_STACK_SERVICE.equals(name)) return mNetworkStack; if (Context.USER_SERVICE.equals(name)) return mUserManager; if (Context.ALARM_SERVICE.equals(name)) return mAlarmManager; + if (Context.LOCATION_SERVICE.equals(name)) return mLocationManager; + if (Context.APP_OPS_SERVICE.equals(name)) return mAppOpsManager; return super.getSystemService(name); } @@ -564,6 +576,7 @@ public class ConnectivityServiceTest { private int mProbesCompleted; private int mProbesSucceeded; private String mNmValidationRedirectUrl = null; + private PersistableBundle mValidationExtras = PersistableBundle.EMPTY; private boolean mNmProvNotificationRequested = false; private final ConditionVariable mNetworkStatusReceived = new ConditionVariable(); @@ -631,8 +644,8 @@ public class ConnectivityServiceTest { } mNmCallbacks.notifyProbeStatusChanged(mProbesCompleted, mProbesSucceeded); - mNmCallbacks.notifyNetworkTested( - mNmValidationResult, mNmValidationRedirectUrl); + mNmCallbacks.notifyNetworkTestedWithExtras( + mNmValidationResult, mNmValidationRedirectUrl, TIMESTAMP, mValidationExtras); if (mNmValidationRedirectUrl != null) { mNmCallbacks.showProvisioningNotification( @@ -970,6 +983,8 @@ public class ConnectivityServiceTest { // not inherit from NetworkAgent. private TestNetworkAgentWrapper mMockNetworkAgent; + private VpnInfo mVpnInfo; + public MockVpn(int userId) { super(startHandlerThreadAndReturnLooper(), mServiceContext, mNetworkManagementService, userId); @@ -1041,6 +1056,17 @@ public class ConnectivityServiceTest { mConnected = false; mConfig = null; } + + @Override + public synchronized VpnInfo getVpnInfo() { + if (mVpnInfo != null) return mVpnInfo; + + return super.getVpnInfo(); + } + + private void setVpnInfo(VpnInfo vpnInfo) { + mVpnInfo = vpnInfo; + } } private void mockVpn(int uid) { @@ -6402,7 +6428,7 @@ public class ConnectivityServiceTest { new NetworkCapabilities(), TYPE_ETHERNET, 0, NetworkRequest.Type.NONE); try { mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, request); + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); fail("registerConnectivityDiagnosticsCallback should throw on invalid NetworkRequest"); } catch (IllegalArgumentException expected) { } @@ -6416,7 +6442,7 @@ public class ConnectivityServiceTest { when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); verify(mIBinder, timeout(TIMEOUT_MS)) .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); @@ -6440,7 +6466,7 @@ public class ConnectivityServiceTest { when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); verify(mIBinder, timeout(TIMEOUT_MS)) .linkToDeath(any(ConnectivityDiagnosticsCallbackInfo.class), anyInt()); @@ -6451,7 +6477,7 @@ public class ConnectivityServiceTest { // Register the same callback again mService.registerConnectivityDiagnosticsCallback( - mConnectivityDiagnosticsCallback, wifiRequest); + mConnectivityDiagnosticsCallback, wifiRequest, mContext.getPackageName()); // Block until all other events are done processing. HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); @@ -6460,4 +6486,145 @@ public class ConnectivityServiceTest { mService.mConnectivityDiagnosticsCallbacks.containsKey( mConnectivityDiagnosticsCallback)); } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkStack() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), null, + mServiceContext, null, null, mService, null, null, null, 0); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + assertTrue( + "NetworkStack permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNoLocationPermission() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), null, + mServiceContext, null, null, mService, null, null, null, 0); + + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + assertFalse( + "ACCESS_FINE_LOCATION permission necessary for Connectivity Diagnostics", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsActiveVpn() throws Exception { + final NetworkAgentInfo naiWithoutUid = + new NetworkAgentInfo( + null, null, null, null, null, new NetworkCapabilities(), null, + mServiceContext, null, null, mService, null, null, null, 0); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // setUp() calls mockVpn() which adds a VPN with the Test Runner's uid. Configure it to be + // active + final VpnInfo info = new VpnInfo(); + info.ownerUid = Process.myUid(); + info.vpnIface = "interface"; + mMockVpn.setVpnInfo(info); + assertTrue( + "Active VPN permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithoutUid, + mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsNetworkAdministrator() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setAdministratorUids(Arrays.asList(Process.myUid())); + final NetworkAgentInfo naiWithUid = + new NetworkAgentInfo( + null, null, null, null, null, nc, null, mServiceContext, null, null, + mService, null, null, null, 0); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // Disconnect mock vpn so the uid check on NetworkAgentInfo is tested + mMockVpn.disconnect(); + assertTrue( + "NetworkCapabilities administrator uid permission not applied", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid(), Process.myUid(), naiWithUid, mContext.getOpPackageName())); + } + + @Test + public void testCheckConnectivityDiagnosticsPermissionsFails() throws Exception { + final NetworkCapabilities nc = new NetworkCapabilities(); + nc.setOwnerUid(Process.myUid()); + nc.setAdministratorUids(Arrays.asList(Process.myUid())); + final NetworkAgentInfo naiWithUid = + new NetworkAgentInfo( + null, null, null, null, null, nc, null, mServiceContext, null, null, + mService, null, null, null, 0); + + setupLocationPermissions(Build.VERSION_CODES.Q, true, AppOpsManager.OPSTR_FINE_LOCATION, + Manifest.permission.ACCESS_FINE_LOCATION); + mServiceContext.setPermission(android.Manifest.permission.NETWORK_STACK, PERMISSION_DENIED); + + // Use wrong pid and uid + assertFalse( + "Permissions allowed when they shouldn't be granted", + mService.checkConnectivityDiagnosticsPermissions( + Process.myPid() + 1, Process.myUid() + 1, naiWithUid, + mContext.getOpPackageName())); + } + + private void setupLocationPermissions( + int targetSdk, boolean locationToggle, String op, String perm) throws Exception { + final ApplicationInfo applicationInfo = new ApplicationInfo(); + applicationInfo.targetSdkVersion = targetSdk; + when(mPackageManager.getApplicationInfoAsUser(anyString(), anyInt(), any())) + .thenReturn(applicationInfo); + + when(mLocationManager.isLocationEnabledForUser(any())).thenReturn(locationToggle); + + when(mAppOpsManager.noteOp(eq(op), eq(Process.myUid()), eq(mContext.getPackageName()))) + .thenReturn(AppOpsManager.MODE_ALLOWED); + + mServiceContext.setPermission(perm, PERMISSION_GRANTED); + } + + @Test + public void testConnectivityDiagnosticsCallbackOnConnectivityReport() throws Exception { + final NetworkRequest request = new NetworkRequest.Builder().build(); + when(mConnectivityDiagnosticsCallback.asBinder()).thenReturn(mIBinder); + + mServiceContext.setPermission( + android.Manifest.permission.NETWORK_STACK, PERMISSION_GRANTED); + + mService.registerConnectivityDiagnosticsCallback( + mConnectivityDiagnosticsCallback, request, mContext.getPackageName()); + + // Block until all other events are done processing. + HandlerUtilsKt.waitForIdle(mCsHandlerThread, TIMEOUT_MS); + + // Connect the cell agent verify that it notifies TestNetworkCallback that it is available + final TestNetworkCallback callback = new TestNetworkCallback(); + mCm.registerDefaultNetworkCallback(callback); + mCellNetworkAgent = new TestNetworkAgentWrapper(TRANSPORT_CELLULAR); + mCellNetworkAgent.connect(true); + callback.expectAvailableThenValidatedCallbacks(mCellNetworkAgent); + callback.assertNoCallback(); + + // Wait for onConnectivityReport to fire + verify(mConnectivityDiagnosticsCallback, timeout(TIMEOUT_MS)) + .onConnectivityReport(any(ConnectivityReport.class)); + } } |