diff options
4 files changed, 319 insertions, 23 deletions
diff --git a/core/java/android/os/INetworkManagementService.aidl b/core/java/android/os/INetworkManagementService.aidl index fca15ac0e2e3..16250c78af9a 100644 --- a/core/java/android/os/INetworkManagementService.aidl +++ b/core/java/android/os/INetworkManagementService.aidl @@ -395,7 +395,7 @@ interface INetworkManagementService void setDefaultNetId(int netId); void clearDefaultNetId(); - void setPermission(boolean internal, boolean changeNetState, in int[] uids); + void setPermission(String permission, in int[] uids); void clearPermission(in int[] uids); /** diff --git a/services/core/java/com/android/server/ConnectivityService.java b/services/core/java/com/android/server/ConnectivityService.java index 85ab2490a797..a9cff22c5937 100644 --- a/services/core/java/com/android/server/ConnectivityService.java +++ b/services/core/java/com/android/server/ConnectivityService.java @@ -137,6 +137,7 @@ import com.android.server.connectivity.Nat464Xlat; import com.android.server.connectivity.NetworkAgentInfo; import com.android.server.connectivity.NetworkMonitor; import com.android.server.connectivity.PacManager; +import com.android.server.connectivity.PermissionMonitor; import com.android.server.connectivity.Tethering; import com.android.server.connectivity.Vpn; import com.android.server.net.BaseNetworkObserver; @@ -225,6 +226,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { private Tethering mTethering; + private final PermissionMonitor mPermissionMonitor; + private KeyStore mKeyStore; @GuardedBy("mVpns") @@ -702,6 +705,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { mTethering = new Tethering(mContext, mNetd, statsService, mHandler.getLooper()); + mPermissionMonitor = new PermissionMonitor(mContext, mNetd); + //set up the listener for user state for creating user VPNs IntentFilter intentFilter = new IntentFilter(); intentFilter.addAction(Intent.ACTION_USER_STARTING); @@ -1484,6 +1489,8 @@ public class ConnectivityService extends IConnectivityManager.Stub { } mHandler.sendMessage(mHandler.obtainMessage(EVENT_SYSTEM_READY)); + + mPermissionMonitor.startMonitoring(); } private BroadcastReceiver mUserPresentReceiver = new BroadcastReceiver() { diff --git a/services/core/java/com/android/server/NetworkManagementService.java b/services/core/java/com/android/server/NetworkManagementService.java index 822007aced91..020c95182d21 100644 --- a/services/core/java/com/android/server/NetworkManagementService.java +++ b/services/core/java/com/android/server/NetworkManagementService.java @@ -16,7 +16,6 @@ package com.android.server; -import static android.Manifest.permission.CHANGE_NETWORK_STATE; import static android.Manifest.permission.CONNECTIVITY_INTERNAL; import static android.Manifest.permission.DUMP; import static android.Manifest.permission.SHUTDOWN; @@ -2059,20 +2058,26 @@ public class NetworkManagementService extends INetworkManagementService.Stub } @Override - public void setPermission(boolean internal, boolean changeNetState, int[] uids) { + public void setPermission(String permission, int[] uids) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - final Command cmd = new Command("network", "permission", "user", "set"); - if (internal) cmd.appendArg(CONNECTIVITY_INTERNAL); - if (changeNetState) cmd.appendArg(CHANGE_NETWORK_STATE); - for (int i=0; i<uids.length; i++) { - cmd.appendArg(uids[i]); - } - - try { - mConnector.execute(cmd); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[4 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "permission"; + argv[1] = "user"; + argv[2] = "set"; + argv[3] = permission; + int argc = 4; + // Avoid overly long commands by limiting number of UIDs per command. + for (int i = 0; i < uids.length; ++i) { + argv[argc++] = uids[i]; + if (i == uids.length - 1 || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 4; + } } } @@ -2080,15 +2085,22 @@ public class NetworkManagementService extends INetworkManagementService.Stub public void clearPermission(int[] uids) { mContext.enforceCallingOrSelfPermission(CONNECTIVITY_INTERNAL, TAG); - final Command cmd = new Command("network", "permission", "user", "clear"); - for (int i=0; i<uids.length; i++) { - cmd.appendArg(uids[i]); - } - - try { - mConnector.execute(cmd); - } catch (NativeDaemonConnectorException e) { - throw e.rethrowAsParcelableException(); + Object[] argv = new Object[3 + MAX_UID_RANGES_PER_COMMAND]; + argv[0] = "permission"; + argv[1] = "user"; + argv[2] = "clear"; + int argc = 3; + // Avoid overly long commands by limiting number of UIDs per command. + for (int i = 0; i < uids.length; ++i) { + argv[argc++] = uids[i]; + if (i == uids.length - 1 || argc == argv.length) { + try { + mConnector.execute("network", Arrays.copyOf(argv, argc)); + } catch (NativeDaemonConnectorException e) { + throw e.rethrowAsParcelableException(); + } + argc = 3; + } } } diff --git a/services/core/java/com/android/server/connectivity/PermissionMonitor.java b/services/core/java/com/android/server/connectivity/PermissionMonitor.java new file mode 100644 index 000000000000..238402f45e87 --- /dev/null +++ b/services/core/java/com/android/server/connectivity/PermissionMonitor.java @@ -0,0 +1,277 @@ +/* + * Copyright (C) 2014 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.CONNECTIVITY_INTERNAL; +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; + +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.pm.UserInfo; +import android.net.Uri; +import android.os.INetworkManagementService; +import android.os.RemoteException; +import android.os.UserHandle; +import android.os.UserManager; +import android.text.TextUtils; +import android.util.Log; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map.Entry; +import java.util.Map; +import java.util.Set; + +/** + * A utility class to inform Netd of UID permisisons. + * Does a mass update at boot and then monitors for app install/remove. + * + * @hide + */ +public class PermissionMonitor { + private static final String TAG = "PermissionMonitor"; + private static final boolean DBG = true; + private static final boolean SYSTEM = true; + private static final boolean NETWORK = false; + + private final Context mContext; + private final PackageManager mPackageManager; + private final UserManager mUserManager; + private final INetworkManagementService mNetd; + private final BroadcastReceiver mIntentReceiver; + + // Values are User IDs. + private final Set<Integer> mUsers = new HashSet<Integer>(); + + // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission. + private final Map<Integer, Boolean> mApps = new HashMap<Integer, Boolean>(); + + public PermissionMonitor(Context context, INetworkManagementService netd) { + mContext = context; + mPackageManager = context.getPackageManager(); + mUserManager = UserManager.get(context); + mNetd = netd; + mIntentReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + String action = intent.getAction(); + int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL); + int appUid = intent.getIntExtra(Intent.EXTRA_UID, -1); + Uri appData = intent.getData(); + String appName = appData != null ? appData.getSchemeSpecificPart() : null; + + if (Intent.ACTION_USER_ADDED.equals(action)) { + onUserAdded(user); + } else if (Intent.ACTION_USER_REMOVED.equals(action)) { + onUserRemoved(user); + } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) { + onAppAdded(appName, appUid); + } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) { + onAppRemoved(appUid); + } + } + }; + } + + // Intended to be called only once at startup, after the system is ready. Installs a broadcast + // receiver to monitor ongoing UID changes, so this shouldn't/needn't be called again. + public synchronized void startMonitoring() { + log("Monitoring"); + + IntentFilter intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_USER_ADDED); + intentFilter.addAction(Intent.ACTION_USER_REMOVED); + mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null); + + intentFilter = new IntentFilter(); + intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED); + intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED); + intentFilter.addDataScheme("package"); + mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null); + + List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS); + if (apps == null) { + loge("No apps"); + return; + } + + for (PackageInfo app : apps) { + int uid = app.applicationInfo != null ? app.applicationInfo.uid : -1; + if (uid < 0) { + continue; + } + + boolean isNetwork = hasNetworkPermission(app); + boolean isSystem = hasSystemPermission(app); + + if (isNetwork || isSystem) { + Boolean permission = mApps.get(uid); + // If multiple packages share a UID (cf: android:sharedUserId) and ask for different + // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is). + if (permission == null || permission == NETWORK) { + mApps.put(uid, isSystem); + } + } + } + + List<UserInfo> users = mUserManager.getUsers(true); // exclude dying users + if (users != null) { + for (UserInfo user : users) { + mUsers.add(user.id); + } + } + + log("Users: " + mUsers.size() + ", Apps: " + mApps.size()); + update(mUsers, mApps, true); + } + + private boolean hasPermission(PackageInfo app, String permission) { + if (app.requestedPermissions != null) { + for (String p : app.requestedPermissions) { + if (permission.equals(p)) { + return true; + } + } + } + return false; + } + + private boolean hasNetworkPermission(PackageInfo app) { + return hasPermission(app, CHANGE_NETWORK_STATE); + } + + private boolean hasSystemPermission(PackageInfo app) { + int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0; + if ((flags & FLAG_SYSTEM) != 0 || (flags & FLAG_UPDATED_SYSTEM_APP) != 0) { + return true; + } + return hasPermission(app, CONNECTIVITY_INTERNAL); + } + + private int[] toIntArray(List<Integer> list) { + int[] array = new int[list.size()]; + for (int i = 0; i < list.size(); i++) { + array[i] = list.get(i); + } + return array; + } + + private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) { + List<Integer> network = new ArrayList<Integer>(); + List<Integer> system = new ArrayList<Integer>(); + for (Entry<Integer, Boolean> app : apps.entrySet()) { + List<Integer> list = app.getValue() ? system : network; + for (int user : users) { + list.add(UserHandle.getUid(user, app.getKey())); + } + } + try { + if (add) { + mNetd.setPermission(CHANGE_NETWORK_STATE, toIntArray(network)); + mNetd.setPermission(CONNECTIVITY_INTERNAL, toIntArray(system)); + } else { + mNetd.clearPermission(toIntArray(network)); + mNetd.clearPermission(toIntArray(system)); + } + } catch (RemoteException e) { + loge("Exception when updating permissions: " + e); + } + } + + private synchronized void onUserAdded(int user) { + if (user < 0) { + loge("Invalid user in onUserAdded: " + user); + return; + } + mUsers.add(user); + + Set<Integer> users = new HashSet<Integer>(); + users.add(user); + update(users, mApps, true); + } + + private synchronized void onUserRemoved(int user) { + if (user < 0) { + loge("Invalid user in onUserRemoved: " + user); + return; + } + mUsers.remove(user); + + Set<Integer> users = new HashSet<Integer>(); + users.add(user); + update(users, mApps, false); + } + + private synchronized void onAppAdded(String appName, int appUid) { + if (TextUtils.isEmpty(appName) || appUid < 0) { + loge("Invalid app in onAppAdded: " + appName + " | " + appUid); + return; + } + + try { + PackageInfo app = mPackageManager.getPackageInfo(appName, GET_PERMISSIONS); + boolean isNetwork = hasNetworkPermission(app); + boolean isSystem = hasSystemPermission(app); + if (isNetwork || isSystem) { + Boolean permission = mApps.get(appUid); + // If multiple packages share a UID (cf: android:sharedUserId) and ask for different + // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is). + if (permission == null || permission == NETWORK) { + mApps.put(appUid, isSystem); + + Map<Integer, Boolean> apps = new HashMap<Integer, Boolean>(); + apps.put(appUid, isSystem); + update(mUsers, apps, true); + } + } + } catch (NameNotFoundException e) { + loge("NameNotFoundException in onAppAdded: " + e); + } + } + + private synchronized void onAppRemoved(int appUid) { + if (appUid < 0) { + loge("Invalid app in onAppRemoved: " + appUid); + return; + } + mApps.remove(appUid); + + Map<Integer, Boolean> apps = new HashMap<Integer, Boolean>(); + apps.put(appUid, NETWORK); // doesn't matter which permission we pick here + update(mUsers, apps, false); + } + + private static void log(String s) { + if (DBG) { + Log.d(TAG, s); + } + } + + private static void loge(String s) { + Log.e(TAG, s); + } +} |