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