Add query to get installed apps with known vuln + tests.
Note that I don't think the query will work correctly across multiple repos, because it is currently only querying the app with the "preferred metadata".
This commit is contained in:
parent
1fc8828122
commit
504854547b
@ -11,6 +11,7 @@ import android.text.TextUtils;
|
|||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import org.fdroid.fdroid.Preferences;
|
import org.fdroid.fdroid.Preferences;
|
||||||
import org.fdroid.fdroid.Utils;
|
import org.fdroid.fdroid.Utils;
|
||||||
|
import org.fdroid.fdroid.data.Schema.ApkAntiFeatureJoinTable;
|
||||||
import org.fdroid.fdroid.data.Schema.ApkTable;
|
import org.fdroid.fdroid.data.Schema.ApkTable;
|
||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
|
import org.fdroid.fdroid.data.Schema.AppMetadataTable;
|
||||||
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
|
import org.fdroid.fdroid.data.Schema.AppMetadataTable.Cols;
|
||||||
@ -133,6 +134,12 @@ public class AppProvider extends FDroidProvider {
|
|||||||
Uri uri = Uri.withAppendedPath(AppProvider.getContentUri(), PATH_CALC_PREFERRED_METADATA);
|
Uri uri = Uri.withAppendedPath(AppProvider.getContentUri(), PATH_CALC_PREFERRED_METADATA);
|
||||||
context.getContentResolver().query(uri, null, null, null, null);
|
context.getContentResolver().query(uri, null, null, null, null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static List<App> findInstalledAppsWithKnownVulns(Context context) {
|
||||||
|
Uri uri = getInstalledWithKnownVulnsUri();
|
||||||
|
Cursor cursor = context.getContentResolver().query(uri, Cols.ALL, null, null, null);
|
||||||
|
return cursorToList(cursor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -150,6 +157,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
protected static class AppQuerySelection extends QuerySelection {
|
protected static class AppQuerySelection extends QuerySelection {
|
||||||
|
|
||||||
private boolean naturalJoinToInstalled;
|
private boolean naturalJoinToInstalled;
|
||||||
|
private boolean naturalJoinAntiFeatures;
|
||||||
private boolean leftJoinPrefs;
|
private boolean leftJoinPrefs;
|
||||||
|
|
||||||
AppQuerySelection() {
|
AppQuerySelection() {
|
||||||
@ -170,6 +178,10 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return naturalJoinToInstalled;
|
return naturalJoinToInstalled;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean naturalJoinAntiFeatures() {
|
||||||
|
return naturalJoinAntiFeatures;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tells the query selection that it will need to join onto the installed apps table
|
* Tells the query selection that it will need to join onto the installed apps table
|
||||||
* when used. This should be called when your query makes use of fields from that table
|
* when used. This should be called when your query makes use of fields from that table
|
||||||
@ -182,6 +194,11 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AppQuerySelection requireNatrualJoinAntiFeatures() {
|
||||||
|
naturalJoinAntiFeatures = true;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
public boolean leftJoinToPrefs() {
|
public boolean leftJoinToPrefs() {
|
||||||
return leftJoinPrefs;
|
return leftJoinPrefs;
|
||||||
}
|
}
|
||||||
@ -201,6 +218,11 @@ public class AppProvider extends FDroidProvider {
|
|||||||
if (this.leftJoinToPrefs() || query.leftJoinToPrefs()) {
|
if (this.leftJoinToPrefs() || query.leftJoinToPrefs()) {
|
||||||
bothWithJoin.requireLeftJoinPrefs();
|
bothWithJoin.requireLeftJoinPrefs();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.naturalJoinAntiFeatures() || query.naturalJoinAntiFeatures()) {
|
||||||
|
bothWithJoin.requireNatrualJoinAntiFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
return bothWithJoin;
|
return bothWithJoin;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -210,6 +232,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
|
|
||||||
private boolean isSuggestedApkTableAdded;
|
private boolean isSuggestedApkTableAdded;
|
||||||
private boolean requiresInstalledTable;
|
private boolean requiresInstalledTable;
|
||||||
|
private boolean requiresAntiFeatures;
|
||||||
private boolean requiresLeftJoinToPrefs;
|
private boolean requiresLeftJoinToPrefs;
|
||||||
private boolean countFieldAppended;
|
private boolean countFieldAppended;
|
||||||
|
|
||||||
@ -243,6 +266,9 @@ public class AppProvider extends FDroidProvider {
|
|||||||
if (selection.leftJoinToPrefs()) {
|
if (selection.leftJoinToPrefs()) {
|
||||||
leftJoinToPrefs();
|
leftJoinToPrefs();
|
||||||
}
|
}
|
||||||
|
if (selection.naturalJoinAntiFeatures()) {
|
||||||
|
naturalJoinAntiFeatures();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: What if the selection requires a natural join, but we first get a left join
|
// TODO: What if the selection requires a natural join, but we first get a left join
|
||||||
@ -277,6 +303,22 @@ public class AppProvider extends FDroidProvider {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void naturalJoinAntiFeatures() {
|
||||||
|
if (!requiresAntiFeatures) {
|
||||||
|
join(
|
||||||
|
getApkAntiFeatureJoinTableName(),
|
||||||
|
"apkAntiFeature",
|
||||||
|
"apkAntiFeature." + ApkAntiFeatureJoinTable.Cols.APK_ID + " = " + getApkTableName() + "." + ApkTable.Cols.ROW_ID);
|
||||||
|
|
||||||
|
join(
|
||||||
|
Schema.AntiFeatureTable.NAME,
|
||||||
|
"antiFeature",
|
||||||
|
"antiFeature." + Schema.AntiFeatureTable.Cols.ROW_ID + " = " + "apkAntiFeature." + ApkAntiFeatureJoinTable.Cols.ANTI_FEATURE_ID);
|
||||||
|
|
||||||
|
requiresAntiFeatures = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addField(String field) {
|
public void addField(String field) {
|
||||||
switch (field) {
|
switch (field) {
|
||||||
@ -370,6 +412,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private static final String PATH_CALC_PREFERRED_METADATA = "calcPreferredMetadata";
|
private static final String PATH_CALC_PREFERRED_METADATA = "calcPreferredMetadata";
|
||||||
private static final String PATH_CALC_SUGGESTED_APKS = "calcNonRepoDetailsFromIndex";
|
private static final String PATH_CALC_SUGGESTED_APKS = "calcNonRepoDetailsFromIndex";
|
||||||
private static final String PATH_TOP_FROM_CATEGORY = "topFromCategory";
|
private static final String PATH_TOP_FROM_CATEGORY = "topFromCategory";
|
||||||
|
private static final String PATH_INSTALLED_WITH_KNOWN_VULNS = "installedWithKnownVulns";
|
||||||
|
|
||||||
private static final int CAN_UPDATE = CODE_SINGLE + 1;
|
private static final int CAN_UPDATE = CODE_SINGLE + 1;
|
||||||
private static final int INSTALLED = CAN_UPDATE + 1;
|
private static final int INSTALLED = CAN_UPDATE + 1;
|
||||||
@ -383,6 +426,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
private static final int HIGHEST_PRIORITY = SEARCH_REPO + 1;
|
private static final int HIGHEST_PRIORITY = SEARCH_REPO + 1;
|
||||||
private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1;
|
private static final int CALC_PREFERRED_METADATA = HIGHEST_PRIORITY + 1;
|
||||||
private static final int TOP_FROM_CATEGORY = CALC_PREFERRED_METADATA + 1;
|
private static final int TOP_FROM_CATEGORY = CALC_PREFERRED_METADATA + 1;
|
||||||
|
private static final int INSTALLED_WITH_KNOWN_VULNS = TOP_FROM_CATEGORY + 1;
|
||||||
|
|
||||||
static {
|
static {
|
||||||
MATCHER.addURI(getAuthority(), null, CODE_LIST);
|
MATCHER.addURI(getAuthority(), null, CODE_LIST);
|
||||||
@ -400,6 +444,7 @@ public class AppProvider extends FDroidProvider {
|
|||||||
MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
|
MATCHER.addURI(getAuthority(), PATH_SPECIFIC_APP + "/#/*", CODE_SINGLE);
|
||||||
MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
|
MATCHER.addURI(getAuthority(), PATH_CALC_PREFERRED_METADATA, CALC_PREFERRED_METADATA);
|
||||||
MATCHER.addURI(getAuthority(), PATH_TOP_FROM_CATEGORY + "/#/*", TOP_FROM_CATEGORY);
|
MATCHER.addURI(getAuthority(), PATH_TOP_FROM_CATEGORY + "/#/*", TOP_FROM_CATEGORY);
|
||||||
|
MATCHER.addURI(getAuthority(), PATH_INSTALLED_WITH_KNOWN_VULNS, INSTALLED_WITH_KNOWN_VULNS);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static Uri getContentUri() {
|
public static Uri getContentUri() {
|
||||||
@ -421,6 +466,12 @@ public class AppProvider extends FDroidProvider {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Uri getInstalledWithKnownVulnsUri() {
|
||||||
|
return getContentUri().buildUpon()
|
||||||
|
.appendPath(PATH_INSTALLED_WITH_KNOWN_VULNS)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
public static Uri getTopFromCategoryUri(String category, int limit) {
|
public static Uri getTopFromCategoryUri(String category, int limit) {
|
||||||
return getContentUri().buildUpon()
|
return getContentUri().buildUpon()
|
||||||
.appendPath(PATH_TOP_FROM_CATEGORY)
|
.appendPath(PATH_TOP_FROM_CATEGORY)
|
||||||
@ -505,6 +556,10 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return ApkTable.NAME;
|
return ApkTable.NAME;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getApkAntiFeatureJoinTableName() {
|
||||||
|
return ApkAntiFeatureJoinTable.NAME;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getProviderName() {
|
protected String getProviderName() {
|
||||||
return "AppProvider";
|
return "AppProvider";
|
||||||
@ -652,6 +707,14 @@ public class AppProvider extends FDroidProvider {
|
|||||||
return new AppQuerySelection(selection, args);
|
return new AppQuerySelection(selection, args);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private AppQuerySelection queryInstalledWithKnownVulns() {
|
||||||
|
// Include the hash in this check because otherwise any app with any vulnerable version will
|
||||||
|
// get returned.
|
||||||
|
String selection = " antiFeature." + Schema.AntiFeatureTable.Cols.NAME + " = 'KnownVuln' AND " +
|
||||||
|
getApkTableName() + "." + ApkTable.Cols.HASH + " = installed." + InstalledAppTable.Cols.HASH;
|
||||||
|
return new AppQuerySelection(selection).requireNaturalInstalledTable().requireNatrualJoinAntiFeatures();
|
||||||
|
}
|
||||||
|
|
||||||
static AppQuerySelection queryPackageNames(String packageNames, String packageNameField) {
|
static AppQuerySelection queryPackageNames(String packageNames, String packageNameField) {
|
||||||
String[] args = packageNames.split(",");
|
String[] args = packageNames.split(",");
|
||||||
String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
|
String selection = packageNameField + " IN (" + generateQuestionMarksForInClause(args.length) + ")";
|
||||||
@ -739,6 +802,11 @@ public class AppProvider extends FDroidProvider {
|
|||||||
includeSwap = false;
|
includeSwap = false;
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
case INSTALLED_WITH_KNOWN_VULNS:
|
||||||
|
selection = selection.add(queryInstalledWithKnownVulns());
|
||||||
|
includeSwap = false;
|
||||||
|
break;
|
||||||
|
|
||||||
case RECENTLY_UPDATED:
|
case RECENTLY_UPDATED:
|
||||||
String table = getTableName();
|
String table = getTableName();
|
||||||
String isNew = table + "." + Cols.LAST_UPDATED + " <= " + table + "." + Cols.ADDED + " DESC";
|
String isNew = table + "." + Cols.LAST_UPDATED + " <= " + table + "." + Cols.ADDED + " DESC";
|
||||||
|
@ -126,6 +126,10 @@ public class TempAppProvider extends AppProvider {
|
|||||||
return TempApkProvider.TABLE_TEMP_APK;
|
return TempApkProvider.TABLE_TEMP_APK;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected String getApkAntiFeatureJoinTableName() {
|
||||||
|
return TempApkProvider.TABLE_TEMP_APK;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Uri insert(Uri uri, ContentValues values) {
|
public Uri insert(Uri uri, ContentValues values) {
|
||||||
switch (MATCHER.match(uri)) {
|
switch (MATCHER.match(uri)) {
|
||||||
|
@ -6,14 +6,17 @@ import android.content.ContentValues;
|
|||||||
import org.fdroid.fdroid.data.Apk;
|
import org.fdroid.fdroid.data.Apk;
|
||||||
import org.fdroid.fdroid.data.ApkProvider;
|
import org.fdroid.fdroid.data.ApkProvider;
|
||||||
import org.fdroid.fdroid.data.App;
|
import org.fdroid.fdroid.data.App;
|
||||||
|
import org.fdroid.fdroid.data.AppProvider;
|
||||||
import org.fdroid.fdroid.data.FDroidProviderTest;
|
import org.fdroid.fdroid.data.FDroidProviderTest;
|
||||||
|
import org.fdroid.fdroid.data.InstalledAppTestUtils;
|
||||||
import org.fdroid.fdroid.data.Schema;
|
import org.fdroid.fdroid.data.Schema;
|
||||||
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.robolectric.RobolectricTestRunner;
|
import org.robolectric.RobolectricTestRunner;
|
||||||
import org.robolectric.annotation.Config;
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static org.junit.Assert.assertNull;
|
import static org.junit.Assert.assertNull;
|
||||||
@ -24,26 +27,88 @@ import static org.junit.Assert.assertEquals;
|
|||||||
@RunWith(RobolectricTestRunner.class)
|
@RunWith(RobolectricTestRunner.class)
|
||||||
public class AntiFeaturesTest extends FDroidProviderTest {
|
public class AntiFeaturesTest extends FDroidProviderTest {
|
||||||
|
|
||||||
@Test
|
private App notVuln;
|
||||||
public void testPerApkAntiFeatures() throws IOException, RepoUpdater.UpdateException {
|
private App allVuln;
|
||||||
|
private App vulnAtV2;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setup() {
|
||||||
|
Preferences.setup(context);
|
||||||
|
|
||||||
ContentValues vulnValues = new ContentValues(1);
|
ContentValues vulnValues = new ContentValues(1);
|
||||||
vulnValues.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
|
vulnValues.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
|
||||||
|
|
||||||
App vulnAtV2 = Assert.insertApp(context, "com.vuln", "Fixed it");
|
vulnAtV2 = Assert.insertApp(context, "com.vuln", "Fixed it");
|
||||||
Assert.insertApk(context, vulnAtV2, 1);
|
insertApk(vulnAtV2, 1, false);
|
||||||
Assert.insertApk(context, vulnAtV2, 2, vulnValues);
|
insertApk(vulnAtV2, 2, true);
|
||||||
Assert.insertApk(context, vulnAtV2, 3);
|
insertApk(vulnAtV2, 3, false);
|
||||||
|
|
||||||
App notVuln = Assert.insertApp(context, "com.not-vuln", "It's Fine");
|
notVuln = Assert.insertApp(context, "com.not-vuln", "It's Fine");
|
||||||
Assert.insertApk(context, notVuln, 5);
|
insertApk(notVuln, 5, false);
|
||||||
Assert.insertApk(context, notVuln, 10);
|
insertApk(notVuln, 10, false);
|
||||||
Assert.insertApk(context, notVuln, 15);
|
insertApk(notVuln, 15, false);
|
||||||
|
|
||||||
App allVuln = Assert.insertApp(context, "com.all-vuln", "Oops");
|
allVuln = Assert.insertApp(context, "com.all-vuln", "Oops");
|
||||||
Assert.insertApk(context, allVuln, 100, vulnValues);
|
insertApk(allVuln, 100, true);
|
||||||
Assert.insertApk(context, allVuln, 101, vulnValues);
|
insertApk(allVuln, 101, true);
|
||||||
Assert.insertApk(context, allVuln, 105, vulnValues);
|
insertApk(allVuln, 105, true);
|
||||||
|
|
||||||
|
AppProvider.Helper.recalculatePreferredMetadata(context);
|
||||||
|
}
|
||||||
|
|
||||||
|
@After
|
||||||
|
public void tearDown() {
|
||||||
|
Preferences.clearSingletonForTesting();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String generateHash(String packageName, int versionCode) {
|
||||||
|
return packageName + "-" + versionCode;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertApk(App app, int versionCode, boolean isVuln) {
|
||||||
|
ContentValues values = new ContentValues();
|
||||||
|
values.put(Schema.ApkTable.Cols.HASH, generateHash(app.packageName, versionCode));
|
||||||
|
if (isVuln) {
|
||||||
|
values.put(Schema.ApkTable.Cols.AntiFeatures.ANTI_FEATURES, "KnownVuln,ContainsGreenButtons");
|
||||||
|
}
|
||||||
|
Assert.insertApk(context, app, versionCode, values);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void install(App app, int versionCode) {
|
||||||
|
String hash = generateHash(app.packageName, versionCode);
|
||||||
|
InstalledAppTestUtils.install(context, app.packageName, versionCode, "v" + versionCode, null, hash);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void noVulnerableApps() {
|
||||||
|
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
|
||||||
|
assertEquals(0, installed.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void futureVersionIsVulnerable() {
|
||||||
|
install(vulnAtV2, 1);
|
||||||
|
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
|
||||||
|
assertEquals(0, installed.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vulnerableAndAbleToBeUpdated() {
|
||||||
|
install(vulnAtV2, 2);
|
||||||
|
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
|
||||||
|
assertEquals(1, installed.size());
|
||||||
|
assertEquals(vulnAtV2.packageName, installed.get(0).packageName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void vulnerableButUpToDate() {
|
||||||
|
install(vulnAtV2, 3);
|
||||||
|
List<App> installed = AppProvider.Helper.findInstalledAppsWithKnownVulns(context);
|
||||||
|
assertEquals(0, installed.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void antiFeaturesSaveCorrectly() {
|
||||||
List<Apk> notVulnApks = ApkProvider.Helper.findByPackageName(context, notVuln.packageName);
|
List<Apk> notVulnApks = ApkProvider.Helper.findByPackageName(context, notVuln.packageName);
|
||||||
assertEquals(3, notVulnApks.size());
|
assertEquals(3, notVulnApks.size());
|
||||||
|
|
||||||
|
@ -22,6 +22,14 @@ public class InstalledAppTestUtils {
|
|||||||
String packageName,
|
String packageName,
|
||||||
int versionCode, String versionName,
|
int versionCode, String versionName,
|
||||||
@Nullable String signingCert) {
|
@Nullable String signingCert) {
|
||||||
|
install(context, packageName, versionCode, versionName, signingCert, null);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static void install(Context context,
|
||||||
|
String packageName,
|
||||||
|
int versionCode, String versionName,
|
||||||
|
@Nullable String signingCert,
|
||||||
|
@Nullable String hash) {
|
||||||
PackageInfo info = new PackageInfo();
|
PackageInfo info = new PackageInfo();
|
||||||
info.packageName = packageName;
|
info.packageName = packageName;
|
||||||
info.versionCode = versionCode;
|
info.versionCode = versionCode;
|
||||||
@ -31,8 +39,12 @@ public class InstalledAppTestUtils {
|
|||||||
if (signingCert != null) {
|
if (signingCert != null) {
|
||||||
info.signatures = new Signature[]{new Signature(signingCert)};
|
info.signatures = new Signature[]{new Signature(signingCert)};
|
||||||
}
|
}
|
||||||
|
|
||||||
String hashType = "sha256";
|
String hashType = "sha256";
|
||||||
String hash = "00112233445566778899aabbccddeeff";
|
if (hash == null) {
|
||||||
|
hash = "00112233445566778899aabbccddeeff";
|
||||||
|
}
|
||||||
|
|
||||||
InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
|
InstalledAppProviderService.insertAppIntoDb(context, info, hashType, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user