Android Intent Firewall


The original version of this documentation is no longer available, so I've created a copy for preservation. Note that this document is quite old, so except dead links and out of date information. People still request it though, so it must contain some value.

Documentation Status

This document is in its release state and may be expanded in the future as needed. This document covers the Intent Firewall as it appears in Android version 4.4.2 (API 19) and is confirmed to be compatible with versions as new as 5.0.2 (API 20). The source code for this component can be found on GrepCode.

Introduction

The Intent Firewall is a component of the Android framework which allows for the enforcement of intents based on rules defined in XML files. As of last modifying this document, Intent Firewall is not an officially supported feature of the Android framework and therefore has no official documentation and could change significantly in future versions. Please refer to Documentation Status to confirm that this document is up to date.

Capabilities of Intent Firewall

Every intent started in the Android framework, including intents created by the operating system, go through the Intent Firewall. This means that the Intent Firewall has the power to allow or deny any intent. The Intent Firewall is also able to dynamically update its rule set as XML files are written to and deleted from the Intent Firewall directory which makes it a very flexible system to configure on the fly.

Limitations of Intent Firewall

The biggest limitation to the Intent Firewall is that it is only accessible via system applications and root users since configuring the firewall requires being able to write directly to the device's filesystem. This is done for obvious security reasons. Even for root users and system applications, writing directly to the filesystem can be challenging because most physical Android devices keep their filesystem mounted in read-only mode during normal operation. This means that in order to configure the Intent Firewall, the filesystem must be remounted into read-write mode. However, most Android devices will also fail to boot if they are left in read-write mode, meaning that the root user or system application must then return the filesystem back to read-only mode once they are done configuring the Intent Firewall. This also raises concerns because it is possible to brick a user's phone should the phone crash while the filesystem is in read-write mode. This makes the Intent Firewall unusable for standard applications intended for release via the Google Play Store and difficult to use for root users and system applications. Phone manufacturers can avoid most of these headaches by directly modifying the operating system image with additional services designed to streamline the writing process to the device's filesystem. The Security Enhancements (SE) for Android project has modified images containing said services, unfortunately SE Android is not fully merged into the standard Android framework.

Another major limitation with the Intent Firewall is that it does not consider the sender when deciding how to handle an incoming intent. It can only consider the details of the intent and its intended receiver.

Lastly, there is no way to conjunctionally combine component filters and intent filters which limits the types of rules that can be defined.

Getting Started

Configuring Android Intent Firewall

How To Load New Rules

Rules need to be placed in the Intent Firewall's rules directory. This can be challenging given the reasons I mentioned in Limitations of Intent Firewall section. Any time a file is written or deleted from this directory, Intent Firewall will automatically look for any files with the .xml extension and try to parse them for rules. This directory is often /data/system/ifw/, but not always. For developers interested in writing applications to interact with the Intent Firewall, you can find the correct directory using the getSystemSecureDirectory() method which belongs to the android.os.Enviornment class. The ifw/ directory will be a subdirectory of the directory returned by getSystemSecureDirectory(). See Intent Firewall Rules for more information on rule syntax.

How To Delete Rules

To delete a rule, you can either remove it from the XML file or delete the XML file all together. The Intent Firewall will automatically detect that a file has been modified or deleted and reload its rule set.

How To Log Intent Firewall

The easiest way to monitor the Intent Firewall is to use the command adb logcat -s IntentFirewall:V. This command will filter all the default logs on the Android device and show the verbose and higher level logs for the Intent Firewall.

Upon writing a valid XML file, a new line should show up in the logs that looks like this:

I/IntentFirewall( 549): Read new rules (A:0 B:0 S:0)

A, B and S correlate the total count of Activity rules, Broadcast rules and Service rules respectively. One important thing to note is that Intent Firewall will only increment these counts for rules which contain an intent filter. Rules containing only a component filter will work, but they will not increment these counters.

Upon writing an invalid XML file, the Intent Firewall's parsers will often throw exceptions. However, do not rely on the Intent Firewall to throw exceptions when bad rules are written since the Intent Firewall does little checking beyond checks for proper XML syntax. It is possible, and quite easy, to write rules that will either never match or always match without the Intent Firewall raising any kind of errors or exceptions.

If you have added rules which have the log attribute set to true, you can see when these rules match by using the command adb logcat -b events -s ifw_intent_matched. When these rules match, a new line in the log will appear which looks like this:

I/ifw_intent_matched( 536): [0,com.android.browser/.BrowserActivity,10008,1,NUL L,android.intent.action.MAIN,NULL,NULL,270532608]

The information dumped in between the square brackets is [intentType, componentName, callerUid, callerPkgCount, callerPkgs, action, mimeType, uri, flags].

Intent Firewall Rules

Format

This is the general format of an XML file containing rules for the Intent Firewall:

<rules>
  <activity block="[true/false]" log="[true/false]" >
    <intent-filter >
      <path literal="[literal]" prefix="[prefix]" sglob="[sglob]" />
      <auth host="[host]" port="[port]" />
      <ssp literal="[literal]" prefix="[prefix]" sglob="[sglob]" />
      <scheme name="[name]" />
      <type name="[name]" />
      <cat name="[category]" />
      <action name="[action]" />
    </intent-filter>
    <component-filter name="[component]" />
  </activity>
</rules>

The rule shown here is for activities, but rules can also target services and broadcasts using the same syntax. Also note that most of these nodes and attributes are not required and most of the time they will not all be used to define a rule. In fact, component-filter and intent-filter have varying degrees of usefulness depending on if your rule is for activities, services, or broadcasts. There are specific examples of rules later in this section.

Important Considerations

Component-filter requires an explicit component name to work. If you are unfamiliar with components, some examples are included in the next section. If an invalid name is set for the component filter, the Intent Firewall will throw an exception. For developers interested in component-filter, you can resolve component names programmatically with the help of the package manager.

Never pass "*" as a name. This will cause the Android device to act unpredictably. If this occurs, remove the bad rule and the phone should recover after some time. To match all, use ".*" or "*/*", but be aware that most attributes will not accept wild cards.

Intent-filter has multiple criteria which must be met in order for a filter to match. See Intent Filter Analysis for more information.

Examples

<!-- block all intents to the android phone dialer -->
<rules>
  <activity block="true" log="false">
    <component-filter name="com.android.dialer/.DialtactsActivity" />
  </activity>
</rules>

<!-- block all intents to the android phone dialer AND android web browser -->
<rules>
  <activity block="true">
    <component-filter name="com.android.browser/.BrowserActivity" />
    <component-filter name="com.android.dialer/.DialtactsActivity" />
  </activity>
</rules>

<!-- block access to settings menu -->
<rules>
  <activity block="true" log="false">
    <component-filter name="com.android.settings/.Settings" />
  </activity>
</rules>

<!-- block and log a specific broadcast -->
<rules>
  <broadcast block="true" log="true">
    <intent-filter>
      <action name="com.example.demoapp.CUSTOM_INTENT" />
    </intent-filter>
  </broadcast>
</rules>

<!-- two rules in one file -->
<rules>
  <activity block="true" log="false">
    <component-filter name="com.example.demoapp/.MainActivity" />
  </activity>
  <broadcast block="true" log="true">
    <intent-filter>
      <action name="com.example.demoapp.CUSTOM_INTENT" />
    </intent-filter>
  </broadcast>
</rules>

Writing and Deploying Your First Rule

In this section, I will provide a step by step guide on how to write and deploy an Intent Firewall rule in the easiest way possible. This guide is intended to provide a starting point for people interested in exploring the Intent Firewall. This guide assumes that you already know how to create emulators using the AVD Manager and have some familiarity with the Android Debugging Bridge (ADB) along with how intents work in the Android framework.

For this guide, I will be using an Android emulator made using AVD Manager. In general, it is easier to work with the Intent Firewall in an emulator because root access is already permitted and the filesystem is already mounted to allow writing.

Our objective will be to write a rule for the Intent Firewall to log whenever the Android device broadcasts that the "Airplane Mode" setting has been changed.

The first step is to write our rule. In this case, we're interested in the Airplane Mode broadcast sent out by the Android device. This particular system broadcast uses the action android.intent.action.AIRPLANE_MODE so this is what we'll write our intent filter to match:

<rules>
  <broadcast log="true">
    <intent-filter>
      <action name="android.intent.action.AIRPLANE_MODE" />
    </intent-filter>
  </broadcast>
</rules>

Save this rule as an XML file like ifw.xml. The name is not important as long as it ends with the XML extension.

The next step is to set up our logging so we can visually confirm when the Intent Firewall reads our rule and when our rule matches the Airplane Mode broadcast. To do this, we'll use two terminals. In one terminal, run the command adb logcat -s IntentFirewall:V. This is where we'll get confirmation that the Intent Firewall has successfully read in our new rule. There will likely already be an entry in this log from when the device first booted up. In the other terminal, run the command adb logcat -b events -s ifw_intent_matched. This is where we'll see when an intent matches our rule.

It is now time to copy our rule onto the emulated device. The easiest way to do this is with the command adb push ifw.xml /data/system/ifw. If everything went well, we should now see an entry in our first terminal saying:

I/IntentFirewall( 549): Read new rules (A:0 B:1 S:0)

Your message might vary slightly. If no message appears, confirm that you copied your file to the correct directory. If XML parser exceptions appear, double check that your XML file contains no typos.

With the rule now loaded into the Intent Firewall, it is time to test it. Turn on Airplane Mode from the device settings and you should see a new message appear in the second terminal.

You have successfully written and deployed your first Intent Firewall rule.

Useful Applications

The following are some high level ideas for what can be done with Intent Firewall:

  • Use component-filter to block an entire application from running.
  • Use intent-filter to block a broadcast so no applications receive it.
  • Use component-filter to prevent the user from accessing their device settings.

Code Analysis

In the following section, I will outline the various classes used in the Intent Firewall and some of their interesting characteristics. This analysis is by no means an exhaustive look at the code and I encourage you to look at the source code yourself if you wish to learn more.

Intent Firewall Analysis

Noteworthy Behaviors

If multiple rules match a particular intent, their log and block settings are combined using or logic. For example, if one matching rule only has block set to true and another matching rule only has log set to true, then the intent will be blocked and logged.

XML Parsing

This class only deals with this portion of the rule:

<rules>
  <activity />
  <service />
  <broadcast />
</rules>

Methods

checkStartActivity() - calls Activity Resolver (extension of Intent Resolver) to decide if an activity should be allowed to start. Passes the Activity Resolver an intent, callerUid, callerPid, resolved type and app uid.

checkService() - checks if a service should be allowed to start using the Service Resolver. Works similarly to checkStartActivty() method.

checkBroadcast() - checks if a broadcast should be allowed to start using the Broadcast Resolver. Works similarly to checkStartActivity() method.

checkIntent() - takes in a resolver, component name, intent type, intent, callerUid, caller pid, resolved type and receiving uid and checks if the intent should be blocked or logged. Does this by querying the resolver for a list of rules and then iterating through them. Logging of intents that should be logged is done in this method. See Rules.query in Intent Firewall Rule Analysis and IntentFirewallResolver.query in Intent Firewall Resolver Analysis.

logIntent() - method called by checkIntent() when an intent should be logged. Tries to get information from the package manager and then pass this information to EventLogTags.writeIfwIntentMatched(). If it catches any exceptions it logs the message "Remote exception while retrieving packages."

getRulesDir() - returns the directory where rules are stored.

readRulesDir() - reads in all files with the extension ".xml" and loads in their rules. New Firewall Intent Resolvers are created so old rules are destroyed. This method iterates through all the files and calls the readRules() method on each file.

readRules() - tries to open the given file and parse the XML file into a temporary list. This method only parses down to the activity/broadcast/service level and confirms that the XML file has no syntax errors. Once the method knows a rule's type (activity, broadcast or service), it calls the Rule.readFromXML() method to parse the rule and if there are no errors it then appends the new rule to the appropriate resolver's temporary rules list. Once all the rules have been parsed, the temporary lists are appended onto the real filter lists for the resolvers. See Rule.readFromXML() in Intent Firewall Rule Analysis.

Intent Firewall Rule Analysis

XML Parsing

This class' parser is intended to be called on individual rules after the Intent Firewall has already parsed the rules layer. The following template only shows the portion of the XML file that is passed to the rule class. See Intent Firewall Analysis for information on the parent layer.

<!-- Activity -->
<activity block="true|false" log="true|false">
  <intent-filter />
  <component-filter name="[component]" />
</activity>

<!-- Broadcast -->
<broadcast name="" block="true|false" log="true|false">
  <intent-filter />
  <component-filter name="[component]" />
</broadcast>

<!-- Service -->
<service name="" block="true|false" log="true|false">
  <intent-filter />
  <component-filter name="[component]" />
</service>

Methods

readFromXML() - looks for a block and/or log attribute in the XML and if found, sets the rule's block and log variables to those values and then returns itself.

readChild() - looks at the current root of the XML and if it's an intent filter, creates a new Firewall Intent Filter and calls its FirewallIntentFilter.readFromXml() method. If the root is a component filter, then it gets the name attribute and adds it to the Component Filter. See FirewallIntentFilter.readFromXml() in Firewall Intent Filter Analysis.

Firewall Intent Filter Analysis

This class in an extension of the Intent Filter class. More information on Intent Filter is available in Intent Filter Analysis.

Methods

FirewallIntentFilter(Rule rule) - Constructor method. Sets the object's rule to the passed in rule.

Intent Filter Analysis

Filter Rules

For an intent-filter to match, three conditions must hold:

  1. The action must match
  2. The category must match
  3. The data type and optionally data scheme must match

Important Considerations

  • An intent-filter with no category will only match an intent with no category.
  • Category will accept wild cards.
  • It is possible for an intent-filter to match without explicitly including a scheme or type node, despite the third condition. This is particularly true when intent-filter is used for broadcast rules.

XML Format

<path literal="[literal]" prefix="[prefix]" sglob="[sglob]" />
<auth host="[host]" port="[port]" />
<ssp literal="[literal]" prefix="[prefix]" sglob="[sglob]" />
<scheme name="[name]" />
<type name="[name]" />
<cat name="[category]" />
<action name="[action]" />

Intent Firewall Resolver Analysis

The Firewall Intent Resolver is an extension of the Intent Resolver class. More information on Intent Resolver is available in Intent Resolver Analysis.

Methods

allowFilterResults() - checks if the rule list contains the intent filter and returns true or false.

Intent Resolver Analysis

Methods

addFilter() - Adds a filter to the resolver.

removeFilter() - Removes a filter from the resolver.

dump() - Prints information on a filter.

queryIntent() - Takes an intent and finds all the filters related to that intent. First, the resolver will look for the MIME type of the intent and if it finds one it'll look up filters for that MIME type. In this check, the resolver looks for a wildcard ("*/") match before checking for filters with matching MIME types. Note, "*/*" will always match. Finally, the resolver will try to match the data URI if one is present. The method does this using four "cuts" and the buildResolveList() method.

buildResolveList() - Starts by taking in an intent and fetching its action, data and package name. Once it has this information, it first looks at the package name and if the filter's package name doesn't match the intent's package name, it drops the filter. If it didn't drop the filter it then checks the destination. Interestingly enough, it only does this to prevent multiple filters from being added for the same target object. Finally, it calls the filter's match() method and sees if the filter matches the intent. If it does, this filter is added to a new rule along with a count of how many conditions it matched and is added to the results list. Finally, the list is sorted based on priority level and stored along with a few lists which contain sub-portions of the overall final list.

Closing Remarks and Areas of Improvement

In this section, I will give my personal opinions on ways that the Intent Firewall could be improved for future versions of Android.

First, some of the messages logged by the intent firewall could be improved. I have outline one such improvement here.

Next, the Intent Filter should require that all the component filters and intent filters in a given rule match in order for the rule to match. It is natural when defining a rule to want to write a component filter to specify a specific component and then add an intent filter to limit specific types of intents to that component. However, with the current logic flow of the Intent Filter, if the component filter matches, the whole rule will match regardless of what was defined in the intent filter. This significantly limits the variety of rules that the Intent Filter can handle. This behavior could be easily implemented using a boolean.

Additionally, the Intent Firewall should be able to consider the sender of the intent when handling an intent. One of the security weaknesses of the current Android framework is that users have no way of tailoring an application's permissions. They're stuck with having to choose between using an application and trusting that the application uses its requested permissions responsibly or not using the application at all. This all or nothing decision is frustrating and unnecessary. It should be noted that a feature called AppOps has been found in the Android framework which provides menus to manually block individual permissions for applications, but this feature is not complete and developers are discouraged from utilizing it. So in the meantime, the Intent Firewall should be improved to be able to block or log intents based on the sender so specific applications can be restricted from accessing sensitive features like cameras, GPS positions and personal information.

I have outlined a possible way of fixing this limitation here.

Lastly, the Intent Firewall should be made more accessible for system applications and root users. There should be services through which they can safely load new rules without having to remount the entire filesystem in read-write mode and risk harming the Android device. There is cause for concern about creating such services and there will need to be some mechanism so only privileged applications can configure the Intent Firewall, but these concerns can be reasonably mitigated. In time, the Intent Firewall could even be opened up to user applications to allow for more robust anti-virus and parental control applications, but the Android community is probably not ready for that since users currently have a bad tendency of installing applications without checking the application's permissions. This would have to be solved in order for it to be safe to allow user applications to request such a large amount of power.