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