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