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]; 107 factoryMap.put(factory.getTagName(), factory); 108 } 109 } 110 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 */ 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 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 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 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, 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 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 */ 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 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 */ 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 */ 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 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 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 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 462 public int getIntentFilterCount() { 463 return mIntentFilters.size(); 464 } 465 466 public FirewallIntentFilter getIntentFilter(int index) { 467 return mIntentFilters.get(index); 468 } 469 470 public int getComponentFilterCount() { 471 return mComponentFilters.size(); 472 } 473 474 public ComponentName getComponentFilter(int index) { 475 return mComponentFilters.get(index); 476 } 477 public boolean getBlock() { 478 return block; 479 } 480 481 public boolean getLog() { 482 return log; 483 } 484 } 485 486 private static class FirewallIntentFilter extends IntentFilter { 487 private final Rule rule; 488 489 public FirewallIntentFilter(Rule rule) { 490 this.rule = rule; 491 } 492 } 493 494 private static class FirewallIntentResolver 495 extends IntentResolver<FirewallIntentFilter, Rule> { 496 @Override 497 protected boolean allowFilterResult(FirewallIntentFilter filter, List<Rule> dest) { 498 return !dest.contains(filter.rule); 499 } 500 501 @Override 502 protected boolean isPackageForFilter(String packageName, FirewallIntentFilter filter) { 503 return true; 504 } 505 506 @Override 507 protected FirewallIntentFilter[] newArray(int size) { 508 return new FirewallIntentFilter[size]; 509 } 510 511 @Override 512 protected Rule newResult(FirewallIntentFilter filter, int match, int userId) { 513 return filter.rule; 514 } 515 516 @Override 517 protected void sortResults(List<Rule> results) { 518 // there's no need to sort the results 519 return; 520 } 521 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 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 { 542 public FirewallHandler(Looper looper) { 543 super(looper, null, true); 544 } 545 546 @Override 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 559 public RuleObserver(File monitoredDir) { 560 super(monitoredDir.getAbsolutePath(), MONITORED_EVENTS); 561 } 562 563 @Override 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 { 581 int checkComponentPermission(String permission, int pid, int uid, 582 int owningUid, boolean exported); 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 */ 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 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