Home | History | Annotate | Download | only in firewall
      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];
    103             factoryMap.put(factory.getTagName(), factory);
    104         }
    105     }
    106 
    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      */
    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 
    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      */
    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 
    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      */
    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      */
    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 
    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
    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
    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 
    370         public int getIntentFilterCount() {
    371             return mIntentFilters.size();
    372         }
    373 
    374         public FirewallIntentFilter getIntentFilter(int index) {
    375             return mIntentFilters.get(index);
    376         }
    377 
    378         public boolean getBlock() {
    379             return block;
    380         }
    381 
    382         public boolean getLog() {
    383             return log;
    384         }
    385     }
    386 
    387     private static class FirewallIntentFilter extends IntentFilter {
    388         private final Rule rule;
    389 
    390         public FirewallIntentFilter(Rule rule) {
    391             this.rule = rule;
    392         }
    393     }
    394 
    395     private static class FirewallIntentResolver
    396             extends IntentResolver<FirewallIntentFilter, Rule> {
    397         @Override
    398         protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) {
    399             return !dest.contains(filter.rule);
    400         }
    401 
    402         @Override
    403         protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) {
    404             return true;
    405         }
    406 
    407         @Override
    408         protected FirewallIntentFilter[] newArray(int size) {
    409             return new FirewallIntentFilter[size];
    410         }
    411 
    412         @Override
    413         protected Rule newResult(FirewallIntentFilter filter, int match, int userId) {
    414             return filter.rule;
    415         }
    416 
    417         @Override
    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 
    452         public RuleObserver(File monitoredFile) {
    453             super(monitoredFile.getParentFile().getAbsolutePath(), CREATED_FLAGS|DELETED_FLAGS);
    454             mMonitoredFile = monitoredFile.getName();
    455         }
    456 
    457         @Override
    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 {
    482         int checkComponentPermission(String permission, int pid, int uid,
    483                 int owningUid, boolean exported);
    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      */
    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 
    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