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