OSDN Git Service

Send app permissions to netd.
authorSreeram Ramachandran <sreeram@google.com>
Wed, 24 Sep 2014 16:16:19 +0000 (09:16 -0700)
committerSreeram Ramachandran <sreeram@google.com>
Thu, 2 Oct 2014 03:22:34 +0000 (20:22 -0700)
Based largely off Robert's http://ag/546170 (thanks!)

Bug: 15413737
Change-Id: I8a1f0a184923c4c0a4935e6b88895bcc05e39f02

core/java/android/os/INetworkManagementService.aidl
services/core/java/com/android/server/ConnectivityService.java
services/core/java/com/android/server/NetworkManagementService.java
services/core/java/com/android/server/connectivity/PermissionMonitor.java [new file with mode: 0644]

index fca15ac..16250c7 100644 (file)
@@ -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);
 
     /**
index 85ab249..a9cff22 100644 (file)
@@ -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() {
index 822007a..020c951 100644 (file)
@@ -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 (file)
index 0000000..238402f
--- /dev/null
@@ -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);
+    }
+}