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

Commit 26aa91ab authored by Chalard Jean's avatar Chalard Jean
Browse files

Have PermissionMonitor arbiter which app can access background networks

This will let ConnectivityService send the right callbacks to the
relevant apps.

Test: manual with apps
      runtest frameworks-net
      cts
      new tests for this functionality
Bug: 67408339

Change-Id: I6f08efd9e73c7e191f833d7f307a3bf4c9e2f0b4
parent 245f8e5a
Loading
Loading
Loading
Loading
+0 −7
Original line number Diff line number Diff line
@@ -1976,13 +1976,6 @@ public class ConnectivityManager {
    /* TODO: These permissions checks don't belong in client-side code. Move them to
     * services.jar, possibly in com.android.server.net. */

    /** {@hide} */
    public static final boolean checkChangePermission(Context context) {
        int uid = Binder.getCallingUid();
        return Settings.checkAndNoteChangeNetworkStateOperation(context, uid, Settings
                .getPackageNameForUid(context, uid), false /* throwException */);
    }

    /** {@hide} */
    public static final void enforceChangePermission(Context context) {
        int uid = Binder.getCallingUid();
+13 −9
Original line number Diff line number Diff line
@@ -1386,6 +1386,12 @@ public class ConnectivityService extends IConnectivityManager.Stub
        }
    }

    private void restrictBackgroundRequestForCaller(NetworkCapabilities nc) {
        if (!mPermissionMonitor.hasUseBackgroundNetworksPermission(Binder.getCallingUid())) {
            nc.addCapability(NET_CAPABILITY_FOREGROUND);
        }
    }

    @Override
    public NetworkState[] getAllNetworkState() {
        // Require internal since we're handing out IMSI details
@@ -4411,15 +4417,13 @@ public class ConnectivityService extends IConnectivityManager.Stub

        NetworkCapabilities nc = new NetworkCapabilities(networkCapabilities);
        restrictRequestUidsForCaller(nc);
        if (!ConnectivityManager.checkChangePermission(mContext)) {
        // Apps without the CHANGE_NETWORK_STATE permission can't use background networks, so
        // make all their listens include NET_CAPABILITY_FOREGROUND. That way, they will get
        // onLost and onAvailable callbacks when networks move in and out of the background.
        // There is no need to do this for requests because an app without CHANGE_NETWORK_STATE
        // can't request networks.
            nc.addCapability(NET_CAPABILITY_FOREGROUND);
        }
        ensureValidNetworkSpecifier(networkCapabilities);
        restrictBackgroundRequestForCaller(nc);
        ensureValidNetworkSpecifier(nc);

        NetworkRequest networkRequest = new NetworkRequest(nc, TYPE_NONE, nextNetworkRequestId(),
                NetworkRequest.Type.LISTEN);
+46 −5
Original line number Diff line number Diff line
@@ -19,6 +19,7 @@ package com.android.server.connectivity;
import static android.Manifest.permission.CHANGE_NETWORK_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.NETWORK_STACK;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
import static android.content.pm.PackageManager.GET_PERMISSIONS;
@@ -27,6 +28,7 @@ import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
@@ -39,6 +41,8 @@ import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;

import com.android.internal.annotations.VisibleForTesting;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
@@ -150,7 +154,14 @@ public class PermissionMonitor {
        update(mUsers, mApps, true);
    }

    private boolean hasPermission(PackageInfo app, String permission) {
    @VisibleForTesting
    boolean isPreinstalledSystemApp(PackageInfo app) {
        int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
        return (flags & (FLAG_SYSTEM | FLAG_UPDATED_SYSTEM_APP)) != 0;
    }

    @VisibleForTesting
    boolean hasPermission(PackageInfo app, String permission) {
        if (app.requestedPermissions != null) {
            for (String p : app.requestedPermissions) {
                if (permission.equals(p)) {
@@ -166,14 +177,40 @@ public class PermissionMonitor {
    }

    private boolean hasRestrictedNetworkPermission(PackageInfo app) {
        int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
        if ((flags & FLAG_SYSTEM) != 0 || (flags & FLAG_UPDATED_SYSTEM_APP) != 0) {
            return true;
        }
        if (isPreinstalledSystemApp(app)) return true;
        return hasPermission(app, CONNECTIVITY_INTERNAL)
                || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS);
    }

    private boolean hasUseBackgroundNetworksPermission(PackageInfo app) {
        // This function defines what it means to hold the permission to use
        // background networks.
        return hasPermission(app, CHANGE_NETWORK_STATE)
                || hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS)
                || hasPermission(app, CONNECTIVITY_INTERNAL)
                || hasPermission(app, NETWORK_STACK)
                // TODO : remove this check (b/31479477). Not all preinstalled apps should
                // have access to background networks, they should just request the appropriate
                // permission for their use case from the list above.
                || isPreinstalledSystemApp(app);
    }

    public boolean hasUseBackgroundNetworksPermission(int uid) {
        final String[] names = mPackageManager.getPackagesForUid(uid);
        if (null == names || names.length == 0) return false;
        try {
            // Only using the first package name. There may be multiple names if multiple
            // apps share the same UID, but in that case they also share permissions so
            // querying with any of the names will return the same results.
            final PackageInfo app = mPackageManager.getPackageInfo(names[0], GET_PERMISSIONS);
            return hasUseBackgroundNetworksPermission(app);
        } catch (NameNotFoundException e) {
            // App not found.
            loge("NameNotFoundException " + names[0], e);
            return false;
        }
    }

    private int[] toIntArray(List<Integer> list) {
        int[] array = new int[list.size()];
        for (int i = 0; i < list.size(); i++) {
@@ -308,4 +345,8 @@ public class PermissionMonitor {
    private static void loge(String s) {
        Log.e(TAG, s);
    }

    private static void loge(String s, Throwable e) {
        Log.e(TAG, s, e);
    }
}
+134 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2018 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.connectivity;

import static android.Manifest.permission.CHANGE_NETWORK_STATE;
import static android.Manifest.permission.CHANGE_WIFI_STATE;
import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
import static android.Manifest.permission.CONNECTIVITY_USE_RESTRICTED_NETWORKS;
import static android.Manifest.permission.NETWORK_STACK;
import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
import static android.content.pm.PackageManager.GET_PERMISSIONS;

import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.mockito.Mockito.when;

import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class PermissionMonitorTest {
    private static final int MOCK_UID = 10001;
    private static final String[] MOCK_PACKAGE_NAMES = new String[] { "com.foo.bar" };

    @Mock private Context mContext;
    @Mock private PackageManager mPackageManager;

    private PermissionMonitor mPermissionMonitor;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        when(mContext.getPackageManager()).thenReturn(mPackageManager);
        when(mPackageManager.getPackagesForUid(MOCK_UID)).thenReturn(MOCK_PACKAGE_NAMES);
        mPermissionMonitor = new PermissionMonitor(mContext, null);
    }

    private void expectPermission(String[] permissions, boolean preinstalled) throws Exception {
        final PackageInfo packageInfo = packageInfoWithPermissions(permissions, preinstalled);
        when(mPackageManager.getPackageInfo(MOCK_PACKAGE_NAMES[0], GET_PERMISSIONS))
                .thenReturn(packageInfo);
    }

    private PackageInfo packageInfoWithPermissions(String[] permissions, boolean preinstalled) {
        final PackageInfo packageInfo = new PackageInfo();
        packageInfo.requestedPermissions = permissions;
        packageInfo.applicationInfo = new ApplicationInfo();
        packageInfo.applicationInfo.flags = preinstalled ? FLAG_SYSTEM : 0;
        return packageInfo;
    }

    @Test
    public void testHasPermission() {
        PackageInfo app = packageInfoWithPermissions(new String[] {}, false);
        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
        assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));

        app = packageInfoWithPermissions(new String[] {
                CHANGE_NETWORK_STATE, NETWORK_STACK
            }, false);
        assertTrue(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
        assertTrue(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
        assertFalse(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));

        app = packageInfoWithPermissions(new String[] {
                CONNECTIVITY_USE_RESTRICTED_NETWORKS, CONNECTIVITY_INTERNAL
            }, false);
        assertFalse(mPermissionMonitor.hasPermission(app, CHANGE_NETWORK_STATE));
        assertFalse(mPermissionMonitor.hasPermission(app, NETWORK_STACK));
        assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_USE_RESTRICTED_NETWORKS));
        assertTrue(mPermissionMonitor.hasPermission(app, CONNECTIVITY_INTERNAL));
    }

    @Test
    public void testIsPreinstalledSystemApp() {
        PackageInfo app = packageInfoWithPermissions(new String[] {}, false);
        assertFalse(mPermissionMonitor.isPreinstalledSystemApp(app));

        app = packageInfoWithPermissions(new String[] {}, true);
        assertTrue(mPermissionMonitor.isPreinstalledSystemApp(app));
    }

    @Test
    public void testHasUseBackgroundNetworksPermission() throws Exception {
        expectPermission(new String[] { CHANGE_NETWORK_STATE }, false);
        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));

        expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, false);
        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));

        // TODO : make this false when b/31479477 is fixed
        expectPermission(new String[] {}, true);
        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
        expectPermission(new String[] { CHANGE_WIFI_STATE }, true);
        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));

        expectPermission(new String[] { NETWORK_STACK, CONNECTIVITY_INTERNAL }, true);
        assertTrue(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));

        expectPermission(new String[] {}, false);
        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));

        expectPermission(new String[] { CHANGE_WIFI_STATE }, false);
        assertFalse(mPermissionMonitor.hasUseBackgroundNetworksPermission(MOCK_UID));
    }
}