Implement checks for calls that may safely mask information

This commit is contained in:
Joe Farebrother
2024-01-19 17:45:39 +00:00
parent 5dd0addfc2
commit 1b13597d72
3 changed files with 94 additions and 66 deletions

View File

@@ -0,0 +1,69 @@
/** Provides classes and predicates for working with Android layouts and UI elements. */
import java
import semmle.code.xml.AndroidManifest
private import semmle.code.java.dataflow.DataFlow
/** An Android Layout XML file. */
class AndroidLayoutXmlFile extends XmlFile {
AndroidLayoutXmlFile() { this.getRelativePath().matches("%/res/layout/%.xml") }
}
/** A component declared in an Android layout file. */
class AndroidLayoutXmlElement extends XmlElement {
AndroidXmlAttribute id;
AndroidLayoutXmlElement() {
this.getFile() instanceof AndroidLayoutXmlFile and
id = this.getAttribute("id")
}
/** Gets the ID of this component. */
string getId() { result = id.getValue() }
/** Gets the class of this component. */
Class getClass() {
this.getName() = "view" and
this.getAttribute("class").getValue() = result.getQualifiedName()
or
this.getName() = result.getQualifiedName()
or
result.hasQualifiedName(["android.widget", "android.view"], this.getName())
}
}
/** An XML element that represents an editable text field. */
class AndroidEditableXmlElement extends AndroidLayoutXmlElement {
AndroidEditableXmlElement() {
this.getClass().getASourceSupertype*().hasQualifiedName("android.widget", "EditText")
}
/** Gets the input type of this field, if any. */
string getInputType() { result = this.getAttribute("inputType").(AndroidXmlAttribute).getValue() }
}
/** A `findViewById` or `requireViewById` method on `Activity` or `View`. */
private class FindViewMethod extends Method {
FindViewMethod() {
this.hasQualifiedName("android.view", "View", ["findViewById", "requireViewById"])
or
exists(Method m |
m.hasQualifiedName("android.app", "Activity", ["findViewById", "requireViewById"]) and
this = m.getAnOverride*()
)
}
}
/** Gets a use of the view that has the given id. (i.e. from a call to a method like `findViewById`) */
MethodCall getAUseOfViewWithId(string id) {
exists(string name, NestedClass r_id, Field id_field |
id = "@+id/" + name and
result.getMethod() instanceof FindViewMethod and
r_id.getEnclosingType().hasName("R") and
r_id.hasName("id") and
id_field.getDeclaringType() = r_id and
id_field.hasName(name)
|
DataFlow::localExprFlow(id_field.getAnAccess(), result.getArgument(0))
)
}

View File

@@ -3,71 +3,7 @@
import java
import semmle.code.java.dataflow.DataFlow
import semmle.code.java.security.SensitiveActions
import semmle.code.xml.AndroidManifest
/** An Android Layout XML file. */
private class AndroidLayoutXmlFile extends XmlFile {
AndroidLayoutXmlFile() { this.getRelativePath().matches("%/res/layout/%.xml") }
}
/** A component declared in an Android layout file. */
class AndroidLayoutXmlElement extends XmlElement {
AndroidXmlAttribute id;
AndroidLayoutXmlElement() {
this.getFile() instanceof AndroidLayoutXmlFile and
id = this.getAttribute("id")
}
/** Gets the ID of this component. */
string getId() { result = id.getValue() }
/** Gets the class of this component. */
Class getClass() {
this.getName() = "view" and
this.getAttribute("class").getValue() = result.getQualifiedName()
or
this.getName() = result.getQualifiedName()
or
result.hasQualifiedName(["android.widget", "android.view"], this.getName())
}
}
/** An XML element that represents an editable text field. */
class AndroidEditableXmlElement extends AndroidLayoutXmlElement {
AndroidEditableXmlElement() {
this.getClass().getASourceSupertype*().hasQualifiedName("android.widget", "EditText")
}
/** Gets the input type of this field, if any. */
string getInputType() { result = this.getAttribute("inputType").(AndroidXmlAttribute).getValue() }
}
/** A `findViewById` or `requireViewById` method on `Activity` or `View`. */
private class FindViewMethod extends Method {
FindViewMethod() {
this.hasQualifiedName("android.view", "View", ["findViewById", "requireViewById"])
or
exists(Method m |
m.hasQualifiedName("android.app", "Activity", ["findViewById", "requireViewById"]) and
this = m.getAnOverride*()
)
}
}
/** Gets a use of the view that has the given id. */
private MethodCall getAUseOfViewWithId(string id) {
exists(string name, NestedClass r_id, Field id_field |
id = "@+id/" + name and
result.getMethod() instanceof FindViewMethod and
r_id.getEnclosingType().hasName("R") and
r_id.hasName("id") and
id_field.getDeclaringType() = r_id and
id_field.hasName(name)
|
DataFlow::localExprFlow(id_field.getAnAccess(), result.getArgument(0))
)
}
import semmle.code.java.frameworks.android.Layout
/** Gets the argument of a use of `setInputType` called on the view with the given id. */
private Argument setInputTypeForId(string id) {

View File

@@ -4,6 +4,7 @@ import java
private import semmle.code.java.dataflow.ExternalFlow
private import semmle.code.java.dataflow.TaintTracking
private import semmle.code.java.security.SensitiveActions
private import semmle.code.java.frameworks.android.Layout
/** A configuration for tracking sensitive information to system notifications. */
private module NotificationTrackingConfig implements DataFlow::ConfigSig {
@@ -42,16 +43,38 @@ private class SetTextCall extends MethodCall {
Expr getStringArgument() { result = this.getArgument(0) }
}
/** A call to a method indicating that the contents of a UI element are safely masked. */
private class MaskCall extends MethodCall {
MaskCall() {
this.getMethod().hasQualifiedName("android.widget", "TextView", "setInputType")
or
this.getMethod().hasQualifiedName("android.widget", "view", "setVisibility")
}
}
/** A configuration for tracking sensitive information to text fields. */
private module TextFieldTrackingConfig implements DataFlow::ConfigSig {
predicate isSource(DataFlow::Node src) { src.asExpr() instanceof SensitiveExpr }
predicate isSink(DataFlow::Node sink) { sink.asExpr() = any(SetTextCall s).getStringArgument() }
predicate isSink(DataFlow::Node sink) {
exists(SetTextCall call |
sink.asExpr() = call.getStringArgument() and
not isMasked(call)
)
}
predicate isBarrier(DataFlow::Node node) {
node.getType() instanceof PrimitiveType or node.getType() instanceof BoxedType
}
}
/** Holds if the qualifier of `call` is also called with a method that may mask the information displayed. */
private predicate isMasked(SetTextCall call) {
exists(string id |
DataFlow::localExprFlow(getAUseOfViewWithId(id), call.getQualifier()) and
DataFlow::localExprFlow(getAUseOfViewWithId(id), any(MaskCall mcall).getQualifier())
)
}
/** Taint tracking flow for sensitive data flowing to text fields. */
module TextFieldTracking = TaintTracking::Global<NotificationTrackingConfig>;