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