1/*
2 * Copyright (C) 2014 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.server.connectivity;
18
19import static android.Manifest.permission.CHANGE_NETWORK_STATE;
20import static android.Manifest.permission.CONNECTIVITY_INTERNAL;
21import static android.content.pm.ApplicationInfo.FLAG_SYSTEM;
22import static android.content.pm.ApplicationInfo.FLAG_UPDATED_SYSTEM_APP;
23import static android.content.pm.PackageManager.GET_PERMISSIONS;
24
25import android.content.BroadcastReceiver;
26import android.content.Context;
27import android.content.Intent;
28import android.content.IntentFilter;
29import android.content.pm.PackageInfo;
30import android.content.pm.PackageManager;
31import android.content.pm.PackageManager.NameNotFoundException;
32import android.content.pm.UserInfo;
33import android.net.Uri;
34import android.os.INetworkManagementService;
35import android.os.RemoteException;
36import android.os.UserHandle;
37import android.os.UserManager;
38import android.text.TextUtils;
39import android.util.Log;
40
41import java.util.ArrayList;
42import java.util.HashMap;
43import java.util.HashSet;
44import java.util.List;
45import java.util.Map.Entry;
46import java.util.Map;
47import java.util.Set;
48
49/**
50 * A utility class to inform Netd of UID permisisons.
51 * Does a mass update at boot and then monitors for app install/remove.
52 *
53 * @hide
54 */
55public class PermissionMonitor {
56    private static final String TAG = "PermissionMonitor";
57    private static final boolean DBG = false;
58    private static final boolean SYSTEM = true;
59    private static final boolean NETWORK = false;
60
61    private final Context mContext;
62    private final PackageManager mPackageManager;
63    private final UserManager mUserManager;
64    private final INetworkManagementService mNetd;
65    private final BroadcastReceiver mIntentReceiver;
66
67    // Values are User IDs.
68    private final Set<Integer> mUsers = new HashSet<Integer>();
69
70    // Keys are App IDs. Values are true for SYSTEM permission and false for NETWORK permission.
71    private final Map<Integer, Boolean> mApps = new HashMap<Integer, Boolean>();
72
73    public PermissionMonitor(Context context, INetworkManagementService netd) {
74        mContext = context;
75        mPackageManager = context.getPackageManager();
76        mUserManager = UserManager.get(context);
77        mNetd = netd;
78        mIntentReceiver = new BroadcastReceiver() {
79            @Override
80            public void onReceive(Context context, Intent intent) {
81                String action = intent.getAction();
82                int user = intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL);
83                int appUid = intent.getIntExtra(Intent.EXTRA_UID, -1);
84                Uri appData = intent.getData();
85                String appName = appData != null ? appData.getSchemeSpecificPart() : null;
86
87                if (Intent.ACTION_USER_ADDED.equals(action)) {
88                    onUserAdded(user);
89                } else if (Intent.ACTION_USER_REMOVED.equals(action)) {
90                    onUserRemoved(user);
91                } else if (Intent.ACTION_PACKAGE_ADDED.equals(action)) {
92                    onAppAdded(appName, appUid);
93                } else if (Intent.ACTION_PACKAGE_REMOVED.equals(action)) {
94                    onAppRemoved(appUid);
95                }
96            }
97        };
98    }
99
100    // Intended to be called only once at startup, after the system is ready. Installs a broadcast
101    // receiver to monitor ongoing UID changes, so this shouldn't/needn't be called again.
102    public synchronized void startMonitoring() {
103        log("Monitoring");
104
105        IntentFilter intentFilter = new IntentFilter();
106        intentFilter.addAction(Intent.ACTION_USER_ADDED);
107        intentFilter.addAction(Intent.ACTION_USER_REMOVED);
108        mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
109
110        intentFilter = new IntentFilter();
111        intentFilter.addAction(Intent.ACTION_PACKAGE_ADDED);
112        intentFilter.addAction(Intent.ACTION_PACKAGE_REMOVED);
113        intentFilter.addDataScheme("package");
114        mContext.registerReceiverAsUser(mIntentReceiver, UserHandle.ALL, intentFilter, null, null);
115
116        List<PackageInfo> apps = mPackageManager.getInstalledPackages(GET_PERMISSIONS);
117        if (apps == null) {
118            loge("No apps");
119            return;
120        }
121
122        for (PackageInfo app : apps) {
123            int uid = app.applicationInfo != null ? app.applicationInfo.uid : -1;
124            if (uid < 0) {
125                continue;
126            }
127
128            boolean isNetwork = hasNetworkPermission(app);
129            boolean isSystem = hasSystemPermission(app);
130
131            if (isNetwork || isSystem) {
132                Boolean permission = mApps.get(uid);
133                // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
134                // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
135                if (permission == null || permission == NETWORK) {
136                    mApps.put(uid, isSystem);
137                }
138            }
139        }
140
141        List<UserInfo> users = mUserManager.getUsers(true);  // exclude dying users
142        if (users != null) {
143            for (UserInfo user : users) {
144                mUsers.add(user.id);
145            }
146        }
147
148        log("Users: " + mUsers.size() + ", Apps: " + mApps.size());
149        update(mUsers, mApps, true);
150    }
151
152    private boolean hasPermission(PackageInfo app, String permission) {
153        if (app.requestedPermissions != null) {
154            for (String p : app.requestedPermissions) {
155                if (permission.equals(p)) {
156                    return true;
157                }
158            }
159        }
160        return false;
161    }
162
163    private boolean hasNetworkPermission(PackageInfo app) {
164        return hasPermission(app, CHANGE_NETWORK_STATE);
165    }
166
167    private boolean hasSystemPermission(PackageInfo app) {
168        int flags = app.applicationInfo != null ? app.applicationInfo.flags : 0;
169        if ((flags & FLAG_SYSTEM) != 0 || (flags & FLAG_UPDATED_SYSTEM_APP) != 0) {
170            return true;
171        }
172        return hasPermission(app, CONNECTIVITY_INTERNAL);
173    }
174
175    private int[] toIntArray(List<Integer> list) {
176        int[] array = new int[list.size()];
177        for (int i = 0; i < list.size(); i++) {
178            array[i] = list.get(i);
179        }
180        return array;
181    }
182
183    private void update(Set<Integer> users, Map<Integer, Boolean> apps, boolean add) {
184        List<Integer> network = new ArrayList<Integer>();
185        List<Integer> system = new ArrayList<Integer>();
186        for (Entry<Integer, Boolean> app : apps.entrySet()) {
187            List<Integer> list = app.getValue() ? system : network;
188            for (int user : users) {
189                list.add(UserHandle.getUid(user, app.getKey()));
190            }
191        }
192        try {
193            if (add) {
194                mNetd.setPermission("NETWORK", toIntArray(network));
195                mNetd.setPermission("SYSTEM", toIntArray(system));
196            } else {
197                mNetd.clearPermission(toIntArray(network));
198                mNetd.clearPermission(toIntArray(system));
199            }
200        } catch (RemoteException e) {
201            loge("Exception when updating permissions: " + e);
202        }
203    }
204
205    private synchronized void onUserAdded(int user) {
206        if (user < 0) {
207            loge("Invalid user in onUserAdded: " + user);
208            return;
209        }
210        mUsers.add(user);
211
212        Set<Integer> users = new HashSet<Integer>();
213        users.add(user);
214        update(users, mApps, true);
215    }
216
217    private synchronized void onUserRemoved(int user) {
218        if (user < 0) {
219            loge("Invalid user in onUserRemoved: " + user);
220            return;
221        }
222        mUsers.remove(user);
223
224        Set<Integer> users = new HashSet<Integer>();
225        users.add(user);
226        update(users, mApps, false);
227    }
228
229    private synchronized void onAppAdded(String appName, int appUid) {
230        if (TextUtils.isEmpty(appName) || appUid < 0) {
231            loge("Invalid app in onAppAdded: " + appName + " | " + appUid);
232            return;
233        }
234
235        try {
236            PackageInfo app = mPackageManager.getPackageInfo(appName, GET_PERMISSIONS);
237            boolean isNetwork = hasNetworkPermission(app);
238            boolean isSystem = hasSystemPermission(app);
239            if (isNetwork || isSystem) {
240                Boolean permission = mApps.get(appUid);
241                // If multiple packages share a UID (cf: android:sharedUserId) and ask for different
242                // permissions, don't downgrade (i.e., if it's already SYSTEM, leave it as is).
243                if (permission == null || permission == NETWORK) {
244                    mApps.put(appUid, isSystem);
245
246                    Map<Integer, Boolean> apps = new HashMap<Integer, Boolean>();
247                    apps.put(appUid, isSystem);
248                    update(mUsers, apps, true);
249                }
250            }
251        } catch (NameNotFoundException e) {
252            loge("NameNotFoundException in onAppAdded: " + e);
253        }
254    }
255
256    private synchronized void onAppRemoved(int appUid) {
257        if (appUid < 0) {
258            loge("Invalid app in onAppRemoved: " + appUid);
259            return;
260        }
261        mApps.remove(appUid);
262
263        Map<Integer, Boolean> apps = new HashMap<Integer, Boolean>();
264        apps.put(appUid, NETWORK);  // doesn't matter which permission we pick here
265        update(mUsers, apps, false);
266    }
267
268    private static void log(String s) {
269        if (DBG) {
270            Log.d(TAG, s);
271        }
272    }
273
274    private static void loge(String s) {
275        Log.e(TAG, s);
276    }
277}
278