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