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