• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2013 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 
17 package com.android.server.firewall;
18 
19 import android.app.AppGlobals;
20 import android.content.ComponentName;
21 import android.content.Intent;
22 import android.content.IntentFilter;
23 import android.content.pm.ActivityInfo;
24 import android.content.pm.ApplicationInfo;
25 import android.content.pm.IPackageManager;
26 import android.content.pm.PackageManager;
27 import android.os.Environment;
28 import android.os.FileObserver;
29 import android.os.Handler;
30 import android.os.Message;
31 import android.os.RemoteException;
32 import android.util.Slog;
33 import android.util.Xml;
34 import com.android.internal.util.XmlUtils;
35 import com.android.server.EventLogTags;
36 import com.android.server.IntentResolver;
37 import org.xmlpull.v1.XmlPullParser;
38 import org.xmlpull.v1.XmlPullParserException;
39 
40 import java.io.File;
41 import java.io.FileInputStream;
42 import java.io.FileNotFoundException;
43 import java.io.IOException;
44 import java.util.ArrayList;
45 import java.util.HashMap;
46 import java.util.List;
47 
48 public class IntentFirewall {
49     private static final String TAG = "IntentFirewall";
50 
51     // e.g. /data/system/ifw/ifw.xml or /data/secure/system/ifw/ifw.xml
52     private static final File RULES_FILE =
53             new File(Environment.getSystemSecureDirectory(), "ifw/ifw.xml");
54 
55     private static final int LOG_PACKAGES_MAX_LENGTH = 150;
56     private static final int LOG_PACKAGES_SUFFICIENT_LENGTH = 125;
57 
58     private static final String TAG_RULES = "rules";
59     private static final String TAG_ACTIVITY = "activity";
60     private static final String TAG_SERVICE = "service";
61     private static final String TAG_BROADCAST = "broadcast";
62 
63     private static final int TYPE_ACTIVITY = 0;
64     private static final int TYPE_BROADCAST = 1;
65     private static final int TYPE_SERVICE = 2;
66 
67     private static final HashMap<String, FilterFactory> factoryMap;
68 
69     private final AMSInterface mAms;
70 
71     private final RuleObserver mObserver;
72 
73     private FirewallIntentResolver mActivityResolver = new FirewallIntentResolver();
74     private FirewallIntentResolver mBroadcastResolver = new FirewallIntentResolver();
75     private FirewallIntentResolver mServiceResolver = new FirewallIntentResolver();
76 
77     static {
78         FilterFactory[] factories = new FilterFactory[] {
79                 AndFilter.FACTORY,
80                 OrFilter.FACTORY,
81                 NotFilter.FACTORY,
82 
83                 StringFilter.ACTION,
84                 StringFilter.COMPONENT,
85                 StringFilter.COMPONENT_NAME,
86                 StringFilter.COMPONENT_PACKAGE,
87                 StringFilter.DATA,
88                 StringFilter.HOST,
89                 StringFilter.MIME_TYPE,
90                 StringFilter.PATH,
91                 StringFilter.SSP,
92 
93                 CategoryFilter.FACTORY,
94                 SenderFilter.FACTORY,
95                 SenderPermissionFilter.FACTORY,
96                 PortFilter.FACTORY
97         };
98 
99         // load factor ~= .75
100         factoryMap = new HashMap<String, FilterFactory>(factories.length * 4 / 3);
101         for (int i=0; i<factories.length; i++) {
102             FilterFactory factory = factories[i];
factory.getTagName()103             factoryMap.put(factory.getTagName(), factory);
104         }
105     }
106 
IntentFirewall(AMSInterface ams)107     public IntentFirewall(AMSInterface ams) {
108         mAms = ams;
109         File rulesFile = getRulesFile();
110         rulesFile.getParentFile().mkdirs();
111 
112         readRules(rulesFile);
113 
114         mObserver = new RuleObserver(rulesFile);
115         mObserver.startWatching();
116     }
117 
118     /**
119      * This is called from ActivityManager to check if a start activity intent should be allowed.
120      * It is assumed the caller is already holding the global ActivityManagerService lock.
121      */
checkStartActivity(Intent intent, ApplicationInfo callerApp, int callerUid, int callerPid, String resolvedType, ActivityInfo resolvedActivity)122     public boolean checkStartActivity(Intent intent, ApplicationInfo callerApp, int callerUid,
123             int callerPid, String resolvedType, ActivityInfo resolvedActivity) {
124         List<Rule> matchingRules = mActivityResolver.queryIntent(intent, resolvedType, false, 0);
125         boolean log = false;
126         boolean block = false;
127 
128         for (int i=0; i< matchingRules.size(); i++) {
129             Rule rule = matchingRules.get(i);
130             if (rule.matches(this, intent, callerApp, callerUid, callerPid, resolvedType,
131                     resolvedActivity.applicationInfo)) {
132                 block |= rule.getBlock();
133                 log |= rule.getLog();
134 
135                 // if we've already determined that we should both block and log, there's no need
136                 // to continue trying rules
137                 if (block && log) {
138                     break;
139                 }
140             }
141         }
142 
143         if (log) {
144             logIntent(TYPE_ACTIVITY, intent, callerUid, resolvedType);
145         }
146 
147         return !block;
148     }
149 
logIntent(int intentType, Intent intent, int callerUid, String resolvedType)150     private static void logIntent(int intentType, Intent intent, int callerUid,
151             String resolvedType) {
152         // The component shouldn't be null, but let's double check just to be safe
153         ComponentName cn = intent.getComponent();
154         String shortComponent = null;
155         if (cn != null) {
156             shortComponent = cn.flattenToShortString();
157         }
158 
159         String callerPackages = null;
160         int callerPackageCount = 0;
161         IPackageManager pm = AppGlobals.getPackageManager();
162         if (pm != null) {
163             try {
164                 String[] callerPackagesArray = pm.getPackagesForUid(callerUid);
165                 if (callerPackagesArray != null) {
166                     callerPackageCount = callerPackagesArray.length;
167                     callerPackages = joinPackages(callerPackagesArray);
168                 }
169             } catch (RemoteException ex) {
170                 Slog.e(TAG, "Remote exception while retrieving packages", ex);
171             }
172         }
173 
174         EventLogTags.writeIfwIntentMatched(intentType, shortComponent, callerUid,
175                 callerPackageCount, callerPackages, intent.getAction(), resolvedType,
176                 intent.getDataString(), intent.getFlags());
177     }
178 
179     /**
180      * Joins a list of package names such that the resulting string is no more than
181      * LOG_PACKAGES_MAX_LENGTH.
182      *
183      * Only full package names will be added to the result, unless every package is longer than the
184      * limit, in which case one of the packages will be truncated and added. In this case, an
185      * additional '-' character will be added to the end of the string, to denote the truncation.
186      *
187      * If it encounters a package that won't fit in the remaining space, it will continue on to the
188      * next package, unless the total length of the built string so far is greater than
189      * LOG_PACKAGES_SUFFICIENT_LENGTH, in which case it will stop and return what it has.
190      */
joinPackages(String[] packages)191     private static String joinPackages(String[] packages) {
192         boolean first = true;
193         StringBuilder sb = new StringBuilder();
194         for (int i=0; i<packages.length; i++) {
195             String pkg = packages[i];
196 
197             // + 1 length for the comma. This logic technically isn't correct for the first entry,
198             // but it's not critical.
199             if (sb.length() + pkg.length() + 1 < LOG_PACKAGES_MAX_LENGTH) {
200                 if (!first) {
201                     sb.append(',');
202                 } else {
203                     first = false;
204                 }
205                 sb.append(pkg);
206             } else if (sb.length() >= LOG_PACKAGES_SUFFICIENT_LENGTH) {
207                 return sb.toString();
208             }
209         }
210         if (sb.length() == 0 && packages.length > 0) {
211             String pkg = packages[0];
212             // truncating from the end - the last part of the package name is more likely to be
213             // interesting/unique
214             return pkg.substring(pkg.length() - LOG_PACKAGES_MAX_LENGTH + 1) + '-';
215         }
216         return null;
217     }
218 
getRulesFile()219     public static File getRulesFile() {
220         return RULES_FILE;
221     }
222 
223     /**
224      * Reads rules from the given file and replaces our set of rules with the newly read rules
225      *
226      * All calls to this method from the file observer come through a handler and are inherently
227      * serialized
228      */
readRules(File rulesFile)229     private void readRules(File rulesFile) {
230         FirewallIntentResolver[] resolvers = new FirewallIntentResolver[3];
231         for (int i=0; i<resolvers.length; i++) {
232             resolvers[i] = new FirewallIntentResolver();
233         }
234 
235         FileInputStream fis;
236         try {
237             fis = new FileInputStream(rulesFile);
238         } catch (FileNotFoundException ex) {
239             // Nope, no rules. Nothing else to do!
240             return;
241         }
242 
243         try {
244             XmlPullParser parser = Xml.newPullParser();
245 
246             parser.setInput(fis, null);
247 
248             XmlUtils.beginDocument(parser, TAG_RULES);
249 
250             int[] numRules = new int[3];
251 
252             int outerDepth = parser.getDepth();
253             while (XmlUtils.nextElementWithin(parser, outerDepth)) {
254                 int ruleType = -1;
255 
256                 String tagName = parser.getName();
257                 if (tagName.equals(TAG_ACTIVITY)) {
258                     ruleType = TYPE_ACTIVITY;
259                 } else if (tagName.equals(TAG_BROADCAST)) {
260                     ruleType = TYPE_BROADCAST;
261                 } else if (tagName.equals(TAG_SERVICE)) {
262                     ruleType = TYPE_SERVICE;
263                 }
264 
265                 if (ruleType != -1) {
266                     Rule rule = new Rule();
267 
268                     FirewallIntentResolver resolver = resolvers[ruleType];
269 
270                     // if we get an error while parsing a particular rule, we'll just ignore
271                     // that rule and continue on with the next rule
272                     try {
273                         rule.readFromXml(parser);
274                     } catch (XmlPullParserException ex) {
275                         Slog.e(TAG, "Error reading intent firewall rule", ex);
276                         continue;
277                     }
278 
279                     numRules[ruleType]++;
280 
281                     for (int i=0; i<rule.getIntentFilterCount(); i++) {
282                         resolver.addFilter(rule.getIntentFilter(i));
283                     }
284                 }
285             }
286 
287             Slog.i(TAG, "Read new rules (A:" + numRules[TYPE_ACTIVITY] +
288                     " B:" + numRules[TYPE_BROADCAST] + " S:" + numRules[TYPE_SERVICE] + ")");
289 
290             synchronized (mAms.getAMSLock()) {
291                 mActivityResolver = resolvers[TYPE_ACTIVITY];
292                 mBroadcastResolver = resolvers[TYPE_BROADCAST];
293                 mServiceResolver = resolvers[TYPE_SERVICE];
294             }
295         } catch (XmlPullParserException ex) {
296             // if there was an error outside of a specific rule, then there are probably
297             // structural problems with the xml file, and we should completely ignore it
298             Slog.e(TAG, "Error reading intent firewall rules", ex);
299             clearRules();
300         } catch (IOException ex) {
301             Slog.e(TAG, "Error reading intent firewall rules", ex);
302             clearRules();
303         } finally {
304             try {
305                 fis.close();
306             } catch (IOException ex) {
307                 Slog.e(TAG, "Error while closing " + rulesFile, ex);
308             }
309         }
310     }
311 
312     /**
313      * Clears out all of our rules
314      *
315      * All calls to this method from the file observer come through a handler and are inherently
316      * serialized
317      */
clearRules()318     private void clearRules() {
319         Slog.i(TAG, "Clearing all rules");
320 
321         synchronized (mAms.getAMSLock())  {
322             mActivityResolver = new FirewallIntentResolver();
323             mBroadcastResolver = new FirewallIntentResolver();
324             mServiceResolver = new FirewallIntentResolver();
325         }
326     }
327 
parseFilter(XmlPullParser parser)328     static Filter parseFilter(XmlPullParser parser) throws IOException, XmlPullParserException {
329         String elementName = parser.getName();
330 
331         FilterFactory factory = factoryMap.get(elementName);
332 
333         if (factory == null) {
334             throw new XmlPullParserException("Unknown element in filter list: " + elementName);
335         }
336         return factory.newFilter(parser);
337     }
338 
339     private static class Rule extends AndFilter {
340         private static final String TAG_INTENT_FILTER = "intent-filter";
341 
342         private static final String ATTR_BLOCK = "block";
343         private static final String ATTR_LOG = "log";
344 
345         private final ArrayList<FirewallIntentFilter> mIntentFilters =
346                 new ArrayList<FirewallIntentFilter>(1);
347         private boolean block;
348         private boolean log;
349 
350         @Override
readFromXml(XmlPullParser parser)351         public Rule readFromXml(XmlPullParser parser) throws IOException, XmlPullParserException {
352             block = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_BLOCK));
353             log = Boolean.parseBoolean(parser.getAttributeValue(null, ATTR_LOG));
354 
355             super.readFromXml(parser);
356             return this;
357         }
358 
359         @Override
readChild(XmlPullParser parser)360         protected void readChild(XmlPullParser parser) throws IOException, XmlPullParserException {
361             if (parser.getName().equals(TAG_INTENT_FILTER)) {
362                 FirewallIntentFilter intentFilter = new FirewallIntentFilter(this);
363                 intentFilter.readFromXml(parser);
364                 mIntentFilters.add(intentFilter);
365             } else {
366                 super.readChild(parser);
367             }
368         }
369 
getIntentFilterCount()370         public int getIntentFilterCount() {
371             return mIntentFilters.size();
372         }
373 
getIntentFilter(int index)374         public FirewallIntentFilter getIntentFilter(int index) {
375             return mIntentFilters.get(index);
376         }
377 
getBlock()378         public boolean getBlock() {
379             return block;
380         }
381 
getLog()382         public boolean getLog() {
383             return log;
384         }
385     }
386 
387     private static class FirewallIntentFilter extends IntentFilter {
388         private final Rule rule;
389 
FirewallIntentFilter(Rule rule)390         public FirewallIntentFilter(Rule rule) {
391             this.rule = rule;
392         }
393     }
394 
395     private static class FirewallIntentResolver
396             extends IntentResolver<FirewallIntentFilter, Rule> {
397         @Override
allowFilterResult(FirewallIntentFilter filter, List<Rule> dest)398         protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) {
399             return !dest.contains(filter.rule);
400         }
401 
402         @Override
isPackageForFilter(String packageName, FirewallIntentFilter filter)403         protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) {
404             return true;
405         }
406 
407         @Override
newArray(int size)408         protected FirewallIntentFilter[] newArray(int size) {
409             return new FirewallIntentFilter[size];
410         }
411 
412         @Override
newResult(FirewallIntentFilter filter, int match, int userId)413         protected Rule newResult(FirewallIntentFilter filter, int match, int userId) {
414             return filter.rule;
415         }
416 
417         @Override
sortResults(List<Rule> results)418         protected void sortResults(List<Rule> results) {
419             // there's no need to sort the results
420             return;
421         }
422     }
423 
424     private static final int READ_RULES = 0;
425     private static final int CLEAR_RULES = 1;
426 
427     final Handler mHandler = new Handler() {
428         @Override
429         public void handleMessage(Message msg) {
430             switch (msg.what) {
431                 case READ_RULES:
432                     readRules(getRulesFile());
433                     break;
434                 case CLEAR_RULES:
435                     clearRules();
436                     break;
437             }
438         }
439     };
440 
441     /**
442      * Monitors for the creation/deletion/modification of the rule file
443      */
444     private class RuleObserver extends FileObserver {
445         // The file name we're monitoring, with no path component
446         private final String mMonitoredFile;
447 
448         private static final int CREATED_FLAGS = FileObserver.CREATE|FileObserver.MOVED_TO|
449                 FileObserver.CLOSE_WRITE;
450         private static final int DELETED_FLAGS = FileObserver.DELETE|FileObserver.MOVED_FROM;
451 
RuleObserver(File monitoredFile)452         public RuleObserver(File monitoredFile) {
453             super(monitoredFile.getParentFile().getAbsolutePath(), CREATED_FLAGS|DELETED_FLAGS);
454             mMonitoredFile = monitoredFile.getName();
455         }
456 
457         @Override
onEvent(int event, String path)458         public void onEvent(int event, String path) {
459             if (path.equals(mMonitoredFile)) {
460                 // we wait 250ms before taking any action on an event, in order to dedup multiple
461                 // events. E.g. a delete event followed by a create event followed by a subsequent
462                 // write+close event;
463                 if ((event & CREATED_FLAGS) != 0) {
464                     mHandler.removeMessages(READ_RULES);
465                     mHandler.removeMessages(CLEAR_RULES);
466                     mHandler.sendEmptyMessageDelayed(READ_RULES, 250);
467                 } else if ((event & DELETED_FLAGS) != 0) {
468                     mHandler.removeMessages(READ_RULES);
469                     mHandler.removeMessages(CLEAR_RULES);
470                     mHandler.sendEmptyMessageDelayed(CLEAR_RULES, 250);
471                 }
472             }
473         }
474     }
475 
476     /**
477      * This interface contains the methods we need from ActivityManagerService. This allows AMS to
478      * export these methods to us without making them public, and also makes it easier to test this
479      * component.
480      */
481     public interface AMSInterface {
checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported)482         int checkComponentPermission(String permission, int pid, int uid,
483                 int owningUid, boolean exported);
getAMSLock()484         Object getAMSLock();
485     }
486 
487     /**
488      * Checks if the caller has access to a component
489      *
490      * @param permission If present, the caller must have this permission
491      * @param pid The pid of the caller
492      * @param uid The uid of the caller
493      * @param owningUid The uid of the application that owns the component
494      * @param exported Whether the component is exported
495      * @return True if the caller can access the described component
496      */
checkComponentPermission(String permission, int pid, int uid, int owningUid, boolean exported)497     boolean checkComponentPermission(String permission, int pid, int uid, int owningUid,
498             boolean exported) {
499         return mAms.checkComponentPermission(permission, pid, uid, owningUid, exported) ==
500                 PackageManager.PERMISSION_GRANTED;
501     }
502 
signaturesMatch(int uid1, int uid2)503     boolean signaturesMatch(int uid1, int uid2) {
504         try {
505             IPackageManager pm = AppGlobals.getPackageManager();
506             return pm.checkUidSignatures(uid1, uid2) == PackageManager.SIGNATURE_MATCH;
507         } catch (RemoteException ex) {
508             Slog.e(TAG, "Remote exception while checking signatures", ex);
509             return false;
510         }
511     }
512 }
513