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