/* * This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html. * * This file is a derivative of code released under the terms listed below. * */ /* * Copyright (c) 2013, * Tobias Blaschke <code@tobiasblaschke.de> * All rights reserved. * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright notice, * this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * 3. The names of the contributors may not be used to endorse or promote * products derived from this software without specific prior written * permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ package com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa; import com.ibm.wala.ipa.callgraph.CGNode; import com.ibm.wala.ipa.callgraph.Context; import com.ibm.wala.ipa.callgraph.ContextItem; import com.ibm.wala.ipa.callgraph.ContextKey; import com.ibm.wala.types.TypeName; import com.ibm.wala.dalvik.util.AndroidComponent; import com.ibm.wala.dalvik.util.AndroidEntryPointManager; import com.ibm.wala.util.strings.Atom; import java.util.Map; import java.util.Set; import java.util.EnumSet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Determines the target of an Android-Intent. * * An Intent is generated at each Constructor-Call of an Android-Intent. Information to that Constructor * is added as target. * * If you want to change the target don't change this Object! Instead place an override using the * AndroidEntryPointManager so no information is lost. * * Upon the creation of an Intent it's target-type almost always points to UNKNOWN. Again change this * using an override. * * This class contains functions that determine the target-type on the Intent itself. They are intended * as fallback only. * * CAUTION: If you inherit from this class - keep hashCodes and equals clashing! * * @see com.ibm.wala.dalvik.util.AndroidEntryPointManager * @see com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.IntentContextSelector * @see com.ibm.wala.dalvik.ipa.callgraph.propagation.cfa.IntentContextInterpreter * * @author Tobias Blaschke <code@tobiasblaschke.de> * @since 2013-10-12 */ public class Intent implements ContextItem, Comparable<Intent> { private static final Logger logger = LoggerFactory.getLogger(Intent.class); /** * Key into the Context that represents the Intent. */ public static final ContextKey INTENT_KEY = new ContextKey() {}; public enum IntentType { /** The target could not be Identified for sure */ UNKNOWN_TARGET, /** The Class the Intent Action refers to belongs to this App */ INTERNAL_TARGET, /** The Class the Intent Action refers to belongs to an external App */ EXTERNAL_TARGET, /** The Action is a Constant defined on the Android-Reference Manual */ STANDARD_ACTION, SYSTEM_SERVICE, /** So External and maybe internal */ BROADCAST, /** Do not handle intent */ IGNORE }; private enum Explicit { UNSET, IMPLICIT, EXPLICIT, /** An other target was set for an explicit Intent */ MULTI } public static final Atom UNBOUND = Atom.findOrCreateAsciiAtom("Unbound"); private Atom action; public Atom uri; private IntentType type; private Explicit explicit = Explicit.UNSET; private AndroidComponent targetCompontent; // Activity, Service, ... private boolean immutable = false; public Intent() { this.action = null; } public Intent(String action) { this(Atom.findOrCreateAsciiAtom(action)); } public Intent(Atom action) { this(action, null); } public Intent(Atom action, Atom uri) { logger.info("Intent({})", action); this.action = action; this.uri = uri; this.type = null; // Delay computation upon it's need this.targetCompontent = null; } public Intent(TypeName action, Atom uri) { this(Atom.findOrCreateAsciiAtom(action.toString()), uri); } public Intent(TypeName action) { this(action, null); } public void setExplicit() { switch (explicit) { case UNSET: // TODO: This is dangerous? explicit = Explicit.EXPLICIT; break; case EXPLICIT: logger.warn("setExplicit was called multiple times on {}", this); unbind(); } } public boolean isExplicit() { return explicit == Explicit.EXPLICIT; } public void setImmutable() { this.immutable = true; } public Intent clone() { final Intent clone = new Intent(); clone.action = this.action; // OK here? clone.uri = this.uri; clone.type = this.type; clone.explicit = this.explicit; clone.targetCompontent = this.targetCompontent; return clone; } /** * Set the explicit target of the intent. * * If setAction is called multible times on an Intent it becomes UNBOUND. */ public void setActionExplicit(Atom action) { if (action == null) { throw new IllegalArgumentException("Action may not be null!"); } if (this.action == null) { assert (this.explicit == Explicit.UNSET) : "No Action but Intent is not UNSET - is " + this.explicit; assert (! immutable) : "Intent was marked immutable - can't change it."; this.action = action; this.explicit = Explicit.EXPLICIT; logger.info("Intent({})", action); } else if (isExplicit() && (! this.action.equals(action))) { // We already have the explicit target. Ignore the change. logger.warn("Explicit Intent {} becomes ubound! Secod action {} requested", this, action); unbind(); } else if (! isExplicit() ) { logger.warn("Making implicit Intent {} explictit! Target: {}", this, action); assert (! immutable) : "Intent was marked immutable - can't change it."; this.action = action; this.explicit = Explicit.EXPLICIT; // TODO: Set type? } else { // Set to same values - OK } } public void unbind() { assert (! immutable) : "Intent was marked immutable - can't change it."; this.action = UNBOUND; this.type = IntentType.UNKNOWN_TARGET; this.explicit = Explicit.MULTI; // XXX shoulb we do this? } /** * Set the target of the intent. * * If setAction is called multible times on an Intent it becomes UNBOUND. */ public void setAction(Atom action) { if (this.action == null) { assert (! immutable) : "Intent was marked immutable - can't change it."; this.action = action; logger.info("Intent({})", action); } else if (isExplicit()) { // We already have the explicit target. Ignore the change. } else if (! action.equals(this.action)) { unbind(); } } public Atom getAction() { if (this.action == null) { assert (! isExplicit()) : "Beeing explicit implies having an action!"; return UNBOUND; } return this.action; } public IntentType getType() { if (this.type != null) { return this.type; } else { if (isSystemService(this)) { this.type = IntentType.SYSTEM_SERVICE; } else if (isStandardAction(this)) { this.type = IntentType.STANDARD_ACTION; } else if (isInternal(this)) { this.type = IntentType.INTERNAL_TARGET; } else if (isExternal(this)) { this.type = IntentType.EXTERNAL_TARGET; } else { this.type = IntentType.UNKNOWN_TARGET; } } return this.type; } /** * Return the type of Component associated with this Intent. * * May return null (especially on an UNKNOWN target). The IntentContextInterpreter uses the * IntentStarters.StartInfo to determine the Target. However it is nicer to set the Component * here. * * @todo Set the Component somewhere */ public AndroidComponent getComponent() { return this.targetCompontent; } private static boolean isSystemService(Intent intent) { assert (intent.action != null); return (intent != null && intent.action != null && (intent.action.getVal(0) != 'L') && (intent.action.rIndex((byte) '/') < 0) && (intent.action.rIndex((byte) '.') < 0)); } /** * Fallback: tries to determine on the Intent itself if it's internal. * * Use {@link #isInternal(boolean)} instead. * * Recomputes if the Intent is internal. * TODO: * @todo Implement it ;) * @todo What to return if it does not, but Summary-Information is available? * @todo We should read in the Manifest.xml rather than relying on the packet name! */ private static boolean isInternal(Intent intent) { // XXX: This may loop forever! /*final Intent override = AndroidEntryPointManager.MANAGER.getIntent(intent); logger.warn("Intent.isInternal(Intent) is an unsafe fallback!"); if (override.getType() != IntentType.UNKNOWN_TARGET) { return override.isInternal(true); // The isInternal defined later not this one! }*/ return false; } /** * Fallback: tries to determine on the Intent itself if it's external. * * Use {@link #isExternal(boolean)} instead. */ private static boolean isExternal(Intent intent) { // XXX: This may loop forever! /*final Intent override = AndroidEntryPointManager.MANAGER.getIntent(intent); logger.warn("Intent.isExternal(Intent) is an unsafe fallback!"); if (override.getType() != IntentType.UNKNOWN_TARGET) { return override.isExternal(true); // The isExternal defined later not this one! }*/ if ((intent.action == null ) || (intent.action.equals(UNBOUND))) { return false; // Is Unknown } String pack = AndroidEntryPointManager.MANAGER.guessPackage(); logger.debug("Is external? {} startsWith {}", intent.action, pack); if (pack == null) { // Unknown so not selected as external return false; } return (! (intent.action.toString().startsWith("L" + pack) || intent.action.toString().startsWith(pack))); } /** * Fallback: tries to determine on the Intent itself if it's a standard action. * * Use {@link #isStandardAction(boolean)} instead. */ private static boolean isStandardAction(Intent intent) { //TODO: This may loop forever! /*final Intent override = AndroidEntryPointManager.MANAGER.getIntent(intent); logger.warn("Intent.isStandardAction(Intent) is an unsafe fallback!"); if (override.getType() != IntentType.UNKNOWN_TARGET) { return override.isStandard(true); }*/ if ((intent.action == null ) || (intent.action.equals(UNBOUND))) { return false; // Is Unknown } // TODO: Make this static or so final Atom andoidIntentAction = Atom.findOrCreateAsciiAtom("Landroid/intent/action"); return intent.action.startsWith(andoidIntentAction); } /** * Is the Intents target internally resolvable. * * @return if the Intent is associated to a class in the analyzed application. * @param strict if false return unknown target as internal */ public boolean isInternal(boolean strict) { IntentType type = getType(); // Asserts type is computed return ((type == IntentType.INTERNAL_TARGET) || ((! strict) && (type == IntentType.UNKNOWN_TARGET))); } /** * Has the target to be resolved by an external App. * * The Intent is not associated to a class in this application or it's a Standard * action defined in the Android Reference Manual. * * @param strict if false return unknown target as external */ public boolean isExternal(boolean strict) { IntentType type = getType(); // Asserts type is computed return ((type == IntentType.EXTERNAL_TARGET) || (type == IntentType.STANDARD_ACTION) || ((! strict) && (type == IntentType.UNKNOWN_TARGET))); } /** * Is the Intent one of the System-Defined ones. * * It's a Standard action defined in the Android Reference Manual. Implies isExternal. * * @param strict if false return unknown target as standard */ public boolean isStandard(boolean strict) { IntentType type = getType(); // Asserts type is computed return ((type == IntentType.STANDARD_ACTION) || ((! strict) && (type == IntentType.UNKNOWN_TARGET))); } @Override public String toString() { StringBuffer ret; if ((this.action == null) || (this.action.equals(UNBOUND))) { return "Unbound Intent"; } else if (getType() == IntentType.SYSTEM_SERVICE) { ret = new StringBuffer("SystemService("); } else { ret = new StringBuffer("Intent("); } ret.append(this.action.toString()); if (uri != null) { ret.append(", "); ret.append(this.uri.toString()); } ret.append(") of type "); ret.append(getType()); return ret.toString(); } /** * CLASHES: Does not consider intent-type. * * This clash is however intended: This aids in resolving the override of an Intent. * The AndroidEntryPointManager generates new Intent Objects. Instead of searching all * overrides we get it for free. */ @Override public int hashCode() { // DO NOT USE TYPE! if (this.uri != null) { return getAction().hashCode() * this.uri.hashCode(); } else { return getAction().hashCode(); } } /** * Does not consider the associated URI. */ public boolean equalAction(Intent other) { return getAction().equals(other.getAction()); } /** * Intents are equal to Intents with other type. * * This clash is however intended: This aids in resolving the override of an Intent. * The AndroidEntryPointManager generates new Intent Objects. Instead of searching all * overrides we get it for free. */ @Override public boolean equals(Object o) { if (o instanceof Intent) { Intent other = (Intent) o; // DO NOT USE TYPE! if (this.uri != null) { return ( (this.uri.equals(other.uri)) && equalAction(other) ); // && (this.explicit == other.explicit)); } else { return ( (other.uri == null) && equalAction(other) ); // && (this.explicit == other.explicit)) ; } } else { System.err.println("WARNING: Can't compare Intent to " + o.getClass()); return false; } } public Intent resolve() { return AndroidEntryPointManager.MANAGER.getIntent(this); } @Override public int compareTo(Intent other) { return getAction().toString().compareTo(other.getAction().toString()); } }