Skip to content

Commit 85526d7

Browse files
committed
Add Fragment injection in PreferenceActivity query
1 parent 701d12f commit 85526d7

10 files changed

+126
-0
lines changed

java/ql/lib/semmle/code/java/security/FragmentInjection.qll

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,32 @@ private import semmle.code.java.frameworks.android.Android
55
private import semmle.code.java.frameworks.android.Fragment
66
private import semmle.code.java.Reflection
77

8+
/** The method `isValidFragment` of the class `android.preference.PreferenceActivity`. */
9+
class IsValidFragmentMethod extends Method {
10+
IsValidFragmentMethod() {
11+
this.getDeclaringType()
12+
.getASupertype*()
13+
.hasQualifiedName("android.preference", "PreferenceActivity") and
14+
this.hasName("isValidFragment")
15+
}
16+
17+
/**
18+
* Holds if this method makes the Activity it is declared in vulnerable to Fragment injection,
19+
* that is, all code paths in this method return `true` and the Activity is exported.
20+
*/
21+
predicate isUnsafe() {
22+
this.getDeclaringType().(AndroidActivity).isExported() and
23+
forex(ReturnStmt retStmt, BooleanLiteral bool |
24+
retStmt.getEnclosingCallable() = this and
25+
// Using taint tracking to handle logical expressions, like
26+
// fragmentName.equals("safe") || true
27+
TaintTracking::localExprTaint(bool, retStmt.getResult())
28+
|
29+
bool.getBooleanValue() = true
30+
)
31+
}
32+
}
33+
834
/**
935
* A sink for Fragment injection vulnerabilities,
1036
* that is, method calls that dynamically add Fragments to Activities.
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
class UnsafeActivity extends PreferenceActivity {
2+
3+
@Override
4+
protected boolean isValidFragment(String fragmentName) {
5+
// BAD: any Fragment name can be provided.
6+
return true;
7+
}
8+
}
9+
10+
11+
class SafeActivity extends PreferenceActivity {
12+
@Override
13+
protected boolean isValidFragment(String fragmentName) {
14+
// Good: only trusted Fragment names are allowed.
15+
return SafeFragment1.class.getName().equals(fragmentName)
16+
|| SafeFragment2.class.getName().equals(fragmentName)
17+
|| SafeFragment3.class.getName().equals(fragmentName);
18+
}
19+
20+
}
21+
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
<include src="FragmentInjection.inc.qhelp"></include>
4+
</qhelp>
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
/**
2+
* @name Android Fragment injection in PreferenceActivity
3+
* @description An insecure implementation of the isValidFragment method
4+
* of the PreferenceActivity class may lead to Fragment injection.
5+
* @kind problem
6+
* @problem.severity error
7+
* @security-severity 9.8
8+
* @precision high
9+
* @id java/android/fragment-injection-preference-activity
10+
* @tags security
11+
* external/cwe/cwe-470
12+
*/
13+
14+
import java
15+
import semmle.code.java.security.FragmentInjection
16+
17+
from IsValidFragmentMethod m
18+
where m.isUnsafe()
19+
select m,
20+
"The 'isValidFragment' method always returns true. This makes the exported Activity $@ vulnerable to Fragment Injection.",
21+
m.getDeclaringType(), m.getDeclaringType().getName()

java/ql/test/query-tests/security/CWE-470/AndroidManifest.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,5 +19,8 @@
1919
<category android:name="android.intent.category.LAUNCHER" />
2020
</intent-filter>
2121
</activity>
22+
<activity android:name=".SafePreferenceActivity" android:exported="true" />
23+
<activity android:name=".UnsafePreferenceActivity" android:exported="true" />
24+
<activity android:name=".UnexportedPreferenceActivity" android:exported="false" />
2225
</application>
2326
</manifest>

java/ql/test/query-tests/security/CWE-470/FragmentInjectionInPreferenceActivityTest.expected

Whitespace-only changes.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
import java
2+
import semmle.code.java.security.FragmentInjection
3+
import TestUtilities.InlineExpectationsTest
4+
5+
class FragmentInjectionInPreferenceActivityTest extends InlineExpectationsTest {
6+
FragmentInjectionInPreferenceActivityTest() { this = "FragmentInjectionInPreferenceActivityTest" }
7+
8+
override string getARelevantTag() { result = "hasPreferenceFragmentInjection" }
9+
10+
override predicate hasActualResult(Location location, string element, string tag, string value) {
11+
tag = "hasPreferenceFragmentInjection" and
12+
exists(IsValidFragmentMethod isValidFragment | isValidFragment.isUnsafe() |
13+
isValidFragment.getLocation() = location and
14+
element = isValidFragment.toString() and
15+
value = ""
16+
)
17+
}
18+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example.myapp;
2+
3+
import android.preference.PreferenceActivity;
4+
5+
public class SafePreferenceActivity extends PreferenceActivity {
6+
7+
@Override
8+
protected boolean isValidFragment(String fragmentName) { // Safe: not all paths return true
9+
return fragmentName.equals("MySafeFragment") || fragmentName.equals("MySafeFragment2");
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example.myapp;
2+
3+
import android.preference.PreferenceActivity;
4+
5+
public class UnexportedPreferenceActivity extends PreferenceActivity {
6+
7+
@Override
8+
protected boolean isValidFragment(String fragmentName) { // Safe: not exported
9+
return true;
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package com.example.myapp;
2+
3+
import android.preference.PreferenceActivity;
4+
5+
public class UnsafePreferenceActivity extends PreferenceActivity {
6+
7+
@Override
8+
protected boolean isValidFragment(String fragmentName) { // $ hasPreferenceFragmentInjection
9+
return fragmentName.equals("MySafeClass") || true;
10+
}
11+
}

0 commit comments

Comments
 (0)